2019-04-14 00:21:56 +08:00
|
|
|
/* Copyright (C) 2019 Mr Goldberg
|
|
|
|
This file is part of the Goldberg Emulator
|
|
|
|
|
|
|
|
The Goldberg Emulator is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 3 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
The Goldberg Emulator 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
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with the Goldberg Emulator; if not, see
|
|
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
|
2023-12-27 15:21:59 +08:00
|
|
|
#include "dll/settings.h"
|
2024-04-21 01:26:55 +08:00
|
|
|
#include "dll/steam_app_ids.h"
|
2019-04-14 00:21:56 +08:00
|
|
|
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
std::string Settings::sanitize(const std::string &name)
|
2019-04-14 00:21:56 +08:00
|
|
|
{
|
2023-11-12 11:19:33 +08:00
|
|
|
// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/io/path.cs#L88C9-L89C1
|
|
|
|
// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/io/pathinternal.cs#L32
|
2024-04-20 06:52:29 +08:00
|
|
|
constexpr const static char InvalidFileNameChars[] = {
|
2023-11-12 11:19:33 +08:00
|
|
|
'\"', '<', '>', '|', '\0',
|
|
|
|
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
|
|
|
|
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
|
|
|
|
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
|
|
|
|
(char)31,
|
|
|
|
':', '*', '?', /*'\\', '/',*/
|
|
|
|
};
|
2019-04-14 00:21:56 +08:00
|
|
|
|
2024-04-20 06:52:29 +08:00
|
|
|
if (name.empty()) return {};
|
|
|
|
|
2023-11-12 11:19:33 +08:00
|
|
|
// we have to use utf-32 because Windows (and probably Linux) allows some chars that need at least 32 bits,
|
|
|
|
// such as this one (U+1F5FA) called "World Map": https://www.compart.com/en/unicode/U+1F5FA
|
|
|
|
// utf-16 encoding for these characters require 2 ushort, but we would like to iterate
|
|
|
|
// over all chars in a linear fashion
|
2024-04-20 06:52:29 +08:00
|
|
|
std::u32string unicode_name{};
|
2023-11-12 11:19:33 +08:00
|
|
|
utf8::utf8to32(
|
|
|
|
name.begin(),
|
|
|
|
utf8::find_invalid(name.begin(), name.end()), // returns an iterator pointing to the first invalid octet
|
2024-04-20 06:52:29 +08:00
|
|
|
std::back_inserter(unicode_name)
|
|
|
|
);
|
2023-11-12 11:19:33 +08:00
|
|
|
|
2024-04-20 06:52:29 +08:00
|
|
|
auto rm_itr = std::remove_if(unicode_name.begin(), unicode_name.end(), [](decltype(unicode_name[0]) ch) {
|
|
|
|
return ch == '\n' || ch == '\r';
|
|
|
|
});
|
|
|
|
if (unicode_name.end() != rm_itr) {
|
|
|
|
unicode_name.erase(rm_itr, unicode_name.end());
|
|
|
|
}
|
2023-11-12 11:19:33 +08:00
|
|
|
|
|
|
|
auto InvalidFileNameChars_last_it = std::end(InvalidFileNameChars);
|
2024-04-20 06:52:29 +08:00
|
|
|
for (auto& uch : unicode_name) {
|
|
|
|
auto found_it = std::find(std::begin(InvalidFileNameChars), InvalidFileNameChars_last_it, uch);
|
2023-11-12 11:19:33 +08:00
|
|
|
if (found_it != InvalidFileNameChars_last_it) { // if illegal
|
2024-04-20 06:52:29 +08:00
|
|
|
uch = ' ';
|
2023-11-12 11:19:33 +08:00
|
|
|
}
|
2019-04-14 00:21:56 +08:00
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
std::string res{};
|
2023-11-12 11:19:33 +08:00
|
|
|
utf8::utf32to8(unicode_name.begin(), unicode_name.end(), std::back_inserter(res));
|
|
|
|
return res;
|
2019-04-14 00:21:56 +08:00
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
Settings::Settings(CSteamID steam_id, CGameID game_id, const std::string &name, const std::string &language, bool offline)
|
2019-04-14 00:21:56 +08:00
|
|
|
{
|
|
|
|
this->steam_id = steam_id;
|
|
|
|
this->game_id = game_id;
|
|
|
|
this->name = sanitize(name);
|
|
|
|
if (this->name.size() == 0) {
|
|
|
|
this->name = " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this->name.size() == 1) {
|
|
|
|
this->name = this->name + " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
auto lang = sanitize(language);
|
|
|
|
std::transform(lang.begin(), lang.end(), lang.begin(), ::tolower);
|
|
|
|
lang.erase(std::remove(lang.begin(), lang.end(), ' '), lang.end());
|
|
|
|
this->language = lang;
|
2019-04-22 04:47:45 +08:00
|
|
|
|
|
|
|
this->offline = offline;
|
2019-04-14 00:21:56 +08:00
|
|
|
}
|
|
|
|
|
2024-03-30 13:55:56 +08:00
|
|
|
// user id
|
2019-04-14 00:21:56 +08:00
|
|
|
CSteamID Settings::get_local_steam_id()
|
|
|
|
{
|
|
|
|
return steam_id;
|
|
|
|
}
|
|
|
|
|
2024-03-30 13:55:56 +08:00
|
|
|
// game id
|
2019-04-14 00:21:56 +08:00
|
|
|
CGameID Settings::get_local_game_id()
|
|
|
|
{
|
|
|
|
return game_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *Settings::get_local_name()
|
|
|
|
{
|
|
|
|
return name.c_str();
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *Settings::get_language()
|
|
|
|
{
|
|
|
|
return language.c_str();
|
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
void Settings::set_local_name(const char *name)
|
2022-08-05 14:09:43 +08:00
|
|
|
{
|
|
|
|
this->name = name;
|
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
void Settings::set_language(const char *language)
|
2022-08-05 14:09:43 +08:00
|
|
|
{
|
|
|
|
this->language = language;
|
|
|
|
}
|
|
|
|
|
2024-04-06 13:55:08 +08:00
|
|
|
void Settings::set_supported_languages(const std::set<std::string> &langs)
|
|
|
|
{
|
|
|
|
this->supported_languages_set = langs;
|
|
|
|
|
|
|
|
this->supported_languages.clear();
|
|
|
|
auto lang_it = langs.cbegin();
|
|
|
|
while (langs.cend() != lang_it) {
|
|
|
|
if (langs.cbegin() == lang_it) this->supported_languages = *lang_it;
|
|
|
|
else this->supported_languages.append(",").append(*lang_it); // this isn't C#, .append() will change the string!
|
|
|
|
++lang_it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::set<std::string>& Settings::get_supported_languages_set() const
|
|
|
|
{
|
|
|
|
return this->supported_languages_set;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& Settings::get_supported_languages() const
|
|
|
|
{
|
|
|
|
return this->supported_languages;
|
|
|
|
}
|
|
|
|
|
2019-04-14 00:21:56 +08:00
|
|
|
void Settings::set_game_id(CGameID game_id)
|
|
|
|
{
|
|
|
|
this->game_id = game_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::set_lobby(CSteamID lobby_id)
|
|
|
|
{
|
|
|
|
this->lobby_id = lobby_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
CSteamID Settings::get_lobby()
|
|
|
|
{
|
|
|
|
return this->lobby_id;
|
|
|
|
}
|
|
|
|
|
2024-03-27 05:48:57 +08:00
|
|
|
bool Settings::is_offline()
|
|
|
|
{
|
|
|
|
return offline;
|
|
|
|
}
|
|
|
|
|
2024-10-22 19:00:49 +08:00
|
|
|
void Settings::set_offline(bool offline)
|
2024-10-22 18:41:46 +08:00
|
|
|
{
|
|
|
|
this->offline = offline;
|
|
|
|
}
|
|
|
|
|
2024-03-27 05:48:57 +08:00
|
|
|
uint16 Settings::get_port()
|
|
|
|
{
|
|
|
|
return port;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::set_port(uint16 port)
|
|
|
|
{
|
|
|
|
this->port = port;
|
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
void Settings::addMod(PublishedFileId_t id, const std::string &title, const std::string &path)
|
2019-04-14 00:21:56 +08:00
|
|
|
{
|
|
|
|
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
|
|
|
|
if (mods.end() != f) {
|
|
|
|
f->title = title;
|
|
|
|
f->path = path;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-01-15 01:39:19 +08:00
|
|
|
Mod_entry new_entry{};
|
2019-04-14 00:21:56 +08:00
|
|
|
new_entry.id = id;
|
|
|
|
new_entry.title = title;
|
|
|
|
new_entry.path = path;
|
|
|
|
mods.push_back(new_entry);
|
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
void Settings::addModDetails(PublishedFileId_t id, const Mod_entry &details)
|
2023-08-21 11:58:55 +08:00
|
|
|
{
|
|
|
|
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
|
|
|
|
if (f != mods.end()) {
|
2024-01-15 01:39:19 +08:00
|
|
|
// don't copy files handles, they're auto generated
|
|
|
|
|
2023-08-21 11:58:55 +08:00
|
|
|
f->fileType = details.fileType;
|
|
|
|
f->description = details.description;
|
|
|
|
f->steamIDOwner = details.steamIDOwner;
|
|
|
|
f->timeCreated = details.timeCreated;
|
|
|
|
f->timeUpdated = details.timeUpdated;
|
|
|
|
f->timeAddedToUserList = details.timeAddedToUserList;
|
|
|
|
f->visibility = details.visibility;
|
|
|
|
f->banned = details.banned;
|
|
|
|
f->acceptedForUse = details.acceptedForUse;
|
|
|
|
f->tagsTruncated = details.tagsTruncated;
|
|
|
|
f->tags = details.tags;
|
|
|
|
f->primaryFileName = details.primaryFileName;
|
|
|
|
f->primaryFileSize = details.primaryFileSize;
|
|
|
|
f->previewFileName = details.previewFileName;
|
|
|
|
f->previewFileSize = details.previewFileSize;
|
|
|
|
f->workshopItemURL = details.workshopItemURL;
|
|
|
|
f->votesUp = details.votesUp;
|
|
|
|
f->votesDown = details.votesDown;
|
|
|
|
f->score = details.score;
|
|
|
|
f->numChildren = details.numChildren;
|
2024-01-15 01:39:19 +08:00
|
|
|
f->previewURL = details.previewURL;
|
2023-08-21 11:58:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-14 00:21:56 +08:00
|
|
|
Mod_entry Settings::getMod(PublishedFileId_t id)
|
|
|
|
{
|
|
|
|
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
|
|
|
|
if (mods.end() != f) {
|
|
|
|
return *f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Mod_entry();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isModInstalled(PublishedFileId_t id)
|
|
|
|
{
|
|
|
|
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
|
|
|
|
if (mods.end() != f) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::set<PublishedFileId_t> Settings::modSet()
|
|
|
|
{
|
|
|
|
std::set<PublishedFileId_t> ret_set;
|
|
|
|
|
|
|
|
for (auto & m: mods) {
|
|
|
|
ret_set.insert(m.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret_set;
|
|
|
|
}
|
|
|
|
|
2024-04-28 04:19:10 +08:00
|
|
|
void Settings::unlockAllDLC(bool value)
|
|
|
|
{
|
|
|
|
this->unlockAllDLCs = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::addDLC(AppId_t appID, std::string name, bool available)
|
|
|
|
{
|
|
|
|
auto f = std::find_if(DLCs.begin(), DLCs.end(), [&appID](DLC_entry const& item) { return item.appID == appID; });
|
|
|
|
if (DLCs.end() != f) {
|
|
|
|
f->name = name;
|
|
|
|
f->available = available;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DLC_entry new_entry{};
|
|
|
|
new_entry.appID = appID;
|
|
|
|
new_entry.name = name;
|
|
|
|
new_entry.available = available;
|
|
|
|
DLCs.push_back(new_entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Settings::DLCCount() const
|
2019-04-14 00:21:56 +08:00
|
|
|
{
|
2024-04-28 04:19:10 +08:00
|
|
|
return static_cast<unsigned int>(this->DLCs.size());
|
2019-04-14 00:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::hasDLC(AppId_t appID)
|
|
|
|
{
|
|
|
|
if (this->unlockAllDLCs) return true;
|
|
|
|
|
|
|
|
auto f = std::find_if(DLCs.begin(), DLCs.end(), [&appID](DLC_entry const& item) { return item.appID == appID; });
|
2024-04-28 04:19:10 +08:00
|
|
|
if (DLCs.end() != f) return f->available;
|
|
|
|
|
|
|
|
if (enable_builtin_preowned_ids && steam_preowned_app_ids.count(appID)) return true;
|
2019-04-14 00:21:56 +08:00
|
|
|
|
2024-04-28 04:19:10 +08:00
|
|
|
return false;
|
2019-04-14 00:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::getDLC(unsigned int index, AppId_t &appID, bool &available, std::string &name)
|
|
|
|
{
|
|
|
|
if (index >= DLCs.size()) return false;
|
|
|
|
|
|
|
|
appID = DLCs[index].appID;
|
|
|
|
available = DLCs[index].available;
|
|
|
|
name = DLCs[index].name;
|
|
|
|
return true;
|
|
|
|
}
|
2019-05-09 20:10:03 +08:00
|
|
|
|
2024-04-28 04:19:10 +08:00
|
|
|
void Settings::assumeAnyAppInstalled(bool val)
|
2023-12-18 21:03:14 +08:00
|
|
|
{
|
2024-04-28 04:19:10 +08:00
|
|
|
assume_any_app_installed = val;
|
2023-12-18 21:03:14 +08:00
|
|
|
}
|
|
|
|
|
2024-04-28 04:19:10 +08:00
|
|
|
void Settings::addInstalledApp(AppId_t appID)
|
2024-04-21 01:26:55 +08:00
|
|
|
{
|
2024-04-28 04:19:10 +08:00
|
|
|
installed_app_ids.insert(appID);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isAppInstalled(AppId_t appID) const
|
|
|
|
{
|
|
|
|
if (assume_any_app_installed) return true;
|
|
|
|
if (installed_app_ids.count(appID)) return true;
|
|
|
|
if (enable_builtin_preowned_ids && steam_preowned_app_ids.count(appID)) return true;
|
|
|
|
|
|
|
|
return false;
|
2024-04-21 01:26:55 +08:00
|
|
|
}
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
void Settings::setAppInstallPath(AppId_t appID, const std::string &path)
|
2019-05-09 20:10:03 +08:00
|
|
|
{
|
|
|
|
app_paths[appID] = path;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Settings::getAppInstallPath(AppId_t appID)
|
|
|
|
{
|
|
|
|
return app_paths[appID];
|
|
|
|
}
|
2019-08-04 06:55:08 +08:00
|
|
|
|
2024-03-30 13:55:56 +08:00
|
|
|
void Settings::setLeaderboard(const std::string &leaderboard, enum ELeaderboardSortMethod sort_method, enum ELeaderboardDisplayType display_type)
|
2019-08-04 06:55:08 +08:00
|
|
|
{
|
2024-03-30 13:55:56 +08:00
|
|
|
Leaderboard_config leader{};
|
2019-08-04 06:55:08 +08:00
|
|
|
leader.sort_method = sort_method;
|
|
|
|
leader.display_type = display_type;
|
|
|
|
|
|
|
|
leaderboards[leaderboard] = leader;
|
2019-09-07 21:39:41 +08:00
|
|
|
}
|
|
|
|
|
2024-03-30 13:55:56 +08:00
|
|
|
const std::map<std::string, Leaderboard_config>& Settings::getLeaderboards() const
|
2024-03-27 05:48:57 +08:00
|
|
|
{
|
|
|
|
return leaderboards;
|
|
|
|
}
|
|
|
|
|
2024-03-30 13:55:56 +08:00
|
|
|
const std::map<std::string, Stat_config>& Settings::getStats() const
|
2024-03-27 05:48:57 +08:00
|
|
|
{
|
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
2024-06-23 05:27:51 +08:00
|
|
|
std::map<std::string, Stat_config>::const_iterator Settings::setStatDefiniton(const std::string &name, const struct Stat_config &stat_config)
|
2024-03-27 05:48:57 +08:00
|
|
|
{
|
2024-06-23 05:27:51 +08:00
|
|
|
auto ins_it = stats.insert_or_assign(common_helpers::ascii_to_lowercase(name), stat_config);
|
|
|
|
return ins_it.first;
|
2024-03-27 05:48:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-03-03 09:32:17 +08:00
|
|
|
int Settings::add_image(const std::string &data, uint32 width, uint32 height)
|
2019-09-07 21:39:41 +08:00
|
|
|
{
|
2024-08-17 23:26:48 +08:00
|
|
|
auto previous_it = std::find_if(images.begin(), images.end(), [&](const std::pair<const size_t, Image_Data> &item) {
|
|
|
|
return item.second.data == data
|
|
|
|
&& item.second.height == height
|
|
|
|
&& item.second.width == width;
|
|
|
|
});
|
|
|
|
if (images.end() != previous_it) {
|
|
|
|
return static_cast<int>(previous_it->first);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Image_Data dt{};
|
2019-09-07 21:39:41 +08:00
|
|
|
dt.width = width;
|
|
|
|
dt.height = height;
|
|
|
|
dt.data = data;
|
2024-08-17 23:26:48 +08:00
|
|
|
|
|
|
|
auto new_handle = images.size() + 1; // never return 0, it is a bad handle for most ISteamUserStats APIs
|
|
|
|
images[new_handle] = dt;
|
|
|
|
|
|
|
|
return static_cast<int>(new_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
Image_Data* Settings::get_image(int handle)
|
|
|
|
{
|
2024-08-21 06:50:47 +08:00
|
|
|
if (INVALID_IMAGE_HANDLE == handle || UNLOADED_IMAGE_HANDLE == handle) {
|
2024-08-18 06:31:28 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-08-17 23:26:48 +08:00
|
|
|
auto image_it = images.find(handle);
|
|
|
|
if (images.end() == image_it) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return &image_it->second;
|
2019-09-07 21:39:41 +08:00
|
|
|
}
|
2023-12-18 11:08:23 +08:00
|
|
|
|
2024-01-22 07:19:41 +08:00
|
|
|
|
|
|
|
void Settings::acceptAnyOverlayInvites(bool value)
|
|
|
|
{
|
|
|
|
auto_accept_any_overlay_invites = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::addFriendToOverlayAutoAccept(uint64_t friend_id)
|
|
|
|
{
|
|
|
|
auto_accept_overlay_invites_friends.insert(friend_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::hasOverlayAutoAcceptInviteFromFriend(uint64_t friend_id) const
|
|
|
|
{
|
|
|
|
if (auto_accept_any_overlay_invites) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return !!auto_accept_overlay_invites_friends.count(friend_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Settings::overlayAutoAcceptInvitesCount() const
|
|
|
|
{
|
|
|
|
return auto_accept_overlay_invites_friends.size();
|
|
|
|
}
|