Launcher: a start map in Settings, GAME, instead of always San Vansterdam

The launcher lists San Vansterdam, the game's other maps and the custom maps of the mods that
load (start_maps.h), keeps the choice with its settings and starts the game in it, as its -map
does: a "load" at the front of the startup commands. A map no longer listed when the world can
load it (a map mod switched off or left out of the merge) is dropped after 20 s, and the game stays
in San Vansterdam. A launcher kept open no longer adds a launch's map to the last one's commands.
Tests: Launcher/Test/start_maps_tests.cpp.
This commit is contained in:
AyeZeeBB committed 2026-10-21 20:12:18 -05:00
1 parent ecacdd4242
commit d1bdb0f8a6
10 files changed
+298 -15

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
// The maps Settings offers to start in, and the "load" the launcher asks the game for.
#include "Launcher/start_maps.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
using namespace dingosdk;
namespace maps = launcher_start_maps;
namespace fs = std::filesystem;
namespace {
int failures = 0;
void check(bool condition, const char* what) {
if (condition) return;
std::cerr << "FAIL: " << what << '\n';
++failures;
}
void write(const fs::path& path, const std::string& text) {
fs::create_directories(path.parent_path());
std::ofstream(path, std::ios::binary) << text;
}
} // namespace
int main(int argc, char** argv) {
const fs::path root = argc > 1 ? fs::path(argv[1]) : fs::temp_directory_path() / "reskate-start-maps-tests";
std::error_code ignored;
fs::remove_all(root, ignored);
// No Mods folder: the game's own maps, San Vansterdam first.
auto list = maps::start_maps(root);
check(list.size() == 6, "The game's six maps are not all offered");
check(!list.empty() && list.front().asset == maps::san_vansterdam && list.front().name == "San Vansterdam",
"San Vansterdam is not first");
check(maps::load_arguments(list.front()).empty(), "San Vansterdam asks for a load");
const auto* grom = maps::find_start_map(list, "levels/game/dingolevel_isle_of_grom/dingolevel_isle_of_grom");
check(grom && grom->name == "Isle of Grom" && grom->mod.empty(), "A retail map is not found whatever the case");
check(grom && maps::load_arguments(*grom) == L"Levels/Game/DingoLevel_Isle_of_Grom/DingoLevel_Isle_of_Grom",
"A retail map does not load by its asset");
write(root / "Mods" / "Map Pack" / "reskate-levels.json", R"({"schema":1,"levels":[
{"asset":"levels/game/dingolevel_reskate_one/dingolevel_reskate_one","displayName":"One","startPoints":["only"]},
{"asset":"levels/game/dingolevel_reskate_two/dingolevel_reskate_two","displayName":"Two","startPoints":["Front Gate","back"]},
{"asset":"levels/game/dingolevel_reskate_three/dingolevel_reskate_three","displayName":"Three","startPoints":["a","b"]}]})");
write(root / "Mods" / "Switched Off" / "reskate-levels.json", R"({"schema":1,"levels":[
{"asset":"levels/game/dingolevel_reskate_off/dingolevel_reskate_off","displayName":"Off","startPoints":[]}]})");
write(root / "Mods" / "Broken" / "reskate-levels.json", R"({"schema":1,"levels":[{"asset":"../x"}]})");
write(root / "Mods" / "mods.json", R"({"schema":1,"mods":[{"name":"Switched Off","enabled":false}]})");
list = maps::start_maps(root);
check(list.size() == 9, "The switched-on map mod's maps are not all offered, or others are");
check(!maps::find_start_map(list, "levels/game/dingolevel_reskate_off/dingolevel_reskate_off"),
"A switched-off mod's map is offered");
const auto* one = maps::find_start_map(list, "levels/game/dingolevel_reskate_one/dingolevel_reskate_one");
check(one && one->name == "One" && one->mod == "Map Pack", "A map mod's map is not named with its mod");
check(one && maps::load_arguments(*one) == L"levels/game/dingolevel_reskate_one/dingolevel_reskate_one",
"A map with one start point is given one");
const auto* two = maps::find_start_map(list, "levels/game/dingolevel_reskate_two/dingolevel_reskate_two");
check(two && maps::load_arguments(*two) == L"levels/game/dingolevel_reskate_two/dingolevel_reskate_two 'Front Gate'",
"A map with several start points does not start at its first, quoted");
const auto* three = maps::find_start_map(list, "levels/game/dingolevel_reskate_three/dingolevel_reskate_three");
check(three && maps::load_arguments(*three) == L"levels/game/dingolevel_reskate_three/dingolevel_reskate_three a",
"A plain start point is quoted");
fs::remove_all(root, ignored);
if (failures) return 1;
std::cout << "start maps: ok\n";
return 0;
}
+6
View File
@@ -5,6 +5,7 @@
#include "launch.h"
#include "text_encoding.h"
#include "mod_profiles.h"
#include "start_maps.h"
#include "thunderstore.h"
#include "Engine/Vfs/mod_conflicts.h"
@@ -194,6 +195,8 @@ struct Settings {
bool gpu_diagnostics{};
bool discord_status{true};
bool offline{};
// The map the game starts in, by its level asset (start_maps.h); empty: San Vansterdam.
std::string start_map;
int menu_key{static_cast<int>(launcher::default_menu_key)};
int console_key{static_cast<int>(launcher::default_console_key)};
int log_level{2};
@@ -433,6 +436,9 @@ struct Ui {
Changelog notes;
Legal legal;
int settings_tab{}; // GAME, DISPLAY, KEYS, ADVANCED, then the game's own pages
// The maps Settings offers to start in, read from the Mods folder when GAME is first drawn
// after Settings opens.
std::optional<std::vector<launcher_start_maps::StartMap>> start_maps;
int binding{}; // 1 = menu key, 2 = console key, while waiting for a press
std::string key_error;
bool mods{};
+28 -2
View File
@@ -1,6 +1,7 @@
#include "gui_internal.h"
#include "depot_output.h"
#include "start_maps.h"
#include "Engine/Core/Json/json.h"
#include "Engine/Core/Log/logging.h"
@@ -50,6 +51,7 @@ Settings load_settings(const fs::path& path) {
settings.gpu_diagnostics = json.value("gpu_diagnostics", settings.gpu_diagnostics);
settings.discord_status = json.value("discord_status", settings.discord_status);
settings.offline = json.value("offline", settings.offline);
settings.start_map = json.value("start_map", std::string());
settings.menu_key = json.value("menu_key", settings.menu_key);
settings.console_key = json.value("console_key", settings.console_key);
if (!launcher::bindable_key(static_cast<unsigned>(settings.menu_key)) ||
@@ -89,6 +91,7 @@ void save_settings(const fs::path& path, const Settings& settings) {
json["gpu_diagnostics"] = settings.gpu_diagnostics;
json["discord_status"] = settings.discord_status;
json["offline"] = settings.offline;
json["start_map"] = settings.start_map;
json["menu_key"] = settings.menu_key;
json["console_key"] = settings.console_key;
json["log_level"] = log_levels[static_cast<std::size_t>(settings.log_level)];
@@ -134,6 +137,29 @@ launcher::LaunchOptions launch_options(const Settings& settings) {
return launcher::parse_launch_options(arguments);
}
// The start map chosen in Settings, as the -map the game is given (empty: San Vansterdam). Read at
// each launch, after the merge: a map whose mod is gone, switched off or left out is not asked for,
// because the game would wait for it for ever; the game then starts in San Vansterdam.
std::wstring start_map_option(const Settings& settings, const fs::path& game_directory) {
if (settings.start_map.empty()) return {};
const auto maps = launcher_start_maps::start_maps(game_directory);
if (const auto* map = launcher_start_maps::find_start_map(maps, settings.start_map)) {
logging::log(logging::Level::info, logging::Channel::launcher, "Start map: {}{}.", map->name,
map->mod.empty() ? "" : " (mod " + map->mod + ")");
return launcher_start_maps::load_arguments(*map);
}
logging::log(logging::Level::warning, logging::Channel::launcher,
"Start map {} is not installed, or its mod is switched off or could not be merged; starting in San Vansterdam.",
settings.start_map);
return {};
}
launcher::LaunchOptions launch_options(const Settings& settings, const fs::path& game_directory) {
auto options = launch_options(settings);
options.map = start_map_option(settings, game_directory);
return options;
}
} // namespace
Launcher::Launcher(const launcher_app::Session& session, std::vector<std::wstring> arguments)
@@ -536,7 +562,7 @@ void Launcher::run_play() {
ignore_mod_problems_ = false;
set(Phase::launching, "Starting Skate");
save();
auto options = launch_options(settings_);
auto options = launch_options(settings_, session_.paths.directory);
// The launcher's own command line can request offline mode too.
if (std::find(arguments_.begin(), arguments_.end(), L"-offline") != arguments_.end() ||
std::find(arguments_.begin(), arguments_.end(), L"--offline") != arguments_.end())
@@ -563,7 +589,7 @@ bool prereleases_enabled(const launcher_app::Session& session) {
return detail::load_settings(detail::settings_path(session)).prereleases;
}
launcher::LaunchOptions saved_launch_options(const launcher_app::Session& session) {
return detail::launch_options(detail::load_settings(detail::settings_path(session)));
return detail::launch_options(detail::load_settings(detail::settings_path(session)), session.paths.directory);
}
void apply_crash_report_setting() noexcept {
try {
+53 -1
View File
@@ -1,5 +1,7 @@
#include "gui_internal.h"
#include "Engine/Game/World/world_names.h"
#include <cmath>
#include <format>
@@ -314,6 +316,54 @@ bool key_bindings(const Fonts& fonts, Settings& settings, Ui& ui) {
return changed;
}
// Settings > GAME: the map the game starts in. Returns true when it changed, so the caller can
// save straight away.
bool start_map_choice(Settings& settings, const launcher_app::Session& session, Ui& ui) {
if (!ui.start_maps) {
try {
ui.start_maps = launcher_start_maps::start_maps(session.paths.directory);
} catch (const std::exception&) {
ui.start_maps.emplace();
}
}
const auto& maps = *ui.start_maps;
const auto* chosen = settings.start_map.empty() ? nullptr : launcher_start_maps::find_start_map(maps, settings.start_map);
std::string preview = settings.start_map.empty() ? world_level_name(launcher_start_maps::san_vansterdam)
: chosen ? chosen->name : world_level_short_name(settings.start_map) + " (not installed)";
bool changed = false;
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Start map");
ImGui::SameLine(S(200));
ImGui::SetNextItemWidth(-1);
if (ImGui::BeginCombo("##start_map", preview.c_str())) {
bool mods_shown = false;
for (std::size_t index = 0; index < maps.size(); ++index) {
const auto& map = maps[index];
if (!map.mod.empty() && !mods_shown) {
mods_shown = true;
ImGui::SeparatorText("MAP MODS");
}
const bool san_vansterdam = map.asset == launcher_start_maps::san_vansterdam;
const bool selected = san_vansterdam ? settings.start_map.empty() : &map == chosen;
ImGui::PushID(static_cast<int>(index));
if (ImGui::Selectable(map.name.c_str(), selected) && !selected) {
settings.start_map = san_vansterdam ? std::string() : map.asset;
changed = true;
}
if (!map.mod.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", map.mod.c_str());
if (selected) ImGui::SetItemDefaultFocus();
ImGui::PopID();
}
ImGui::EndCombo();
}
setting_note(!settings.start_map.empty() && !chosen ?
"This map's mod is not installed, is switched off, or could not be merged: Skate starts in San Vansterdam "
"until it is back." :
"skate. always opens in San Vansterdam; another map loads as soon as the game is up. A map mod that is "
"switched off or removed later falls back to San Vansterdam.");
return changed;
}
} // namespace
void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui, HWND window) {
@@ -387,6 +437,7 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui
settings.loose_files);
setting_check("Discord status", "Show on your Discord profile where you are skating: the map, the server or lobby "
"and how many are in it. Off, the game does not talk to Discord at all.", settings.discord_status);
if (start_map_choice(settings, launcher.session(), ui)) launcher.save();
ImGui::Spacing();
section_caption(fonts, "EXTRA GAME ARGUMENTS");
std::array<char, 1024> arguments{};
@@ -497,8 +548,9 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui
if (!open) {
launcher.save();
launcher.check_again_if_update_choices_changed();
// Read again the next time: the game may have run in between.
// Read again the next time: the game may have run in between, and mods may have changed.
ui.game.loaded = false;
ui.start_maps.reset();
}
}
+9 -8
View File
@@ -852,14 +852,15 @@ DWORD start_game(const Session& session, const launcher::LaunchOptions& options,
}
set_environment(L"RESKATE_GPU_DIAGNOSTICS", options.gpu_diagnostics ? L"1" : L"0");
// -map: the game loads it once it is up, as the console's "load" would (client_tick.cpp),
// ahead of any startup commands already asked for.
if (!options.map.empty()) {
std::wstring commands = L"load " + options.map;
std::wstring already(4096, L'\0');
const auto length = GetEnvironmentVariableW(L"RESKATE_STARTUP_COMMANDS", already.data(), 4096);
if (length && length < 4096) commands += L";" + already.substr(0, length);
set_environment(L"RESKATE_STARTUP_COMMANDS", commands.c_str());
}
// ahead of any startup commands already asked for. Those are the launcher's own, read once: a
// launcher kept open sets the variable again at each launch, and must not add to the last one.
static const std::wstring asked = [] {
std::wstring value(4096, L'\0');
const auto length = GetEnvironmentVariableW(L"RESKATE_STARTUP_COMMANDS", value.data(), 4096);
return length && length < 4096 ? value.substr(0, length) : std::wstring{};
}();
std::wstring commands = options.map.empty() ? asked : L"load " + options.map + (asked.empty() ? L"" : L";" + asked);
set_environment(L"RESKATE_STARTUP_COMMANDS", commands.empty() ? nullptr : commands.c_str());
set_environment(L"RESKATE_DISCORD", options.discord ? L"1" : L"0");
if (options.menu_key == options.console_key)
throw std::runtime_error("The menu and console need different keys. Change one in Settings.");
+73
View File
@@ -0,0 +1,73 @@
#include "start_maps.h"
#include "Engine/Game/World/custom_level_manifest.h"
#include "Engine/Game/World/world_names.h"
#include "Engine/Vfs/mod_list.h"
#include <algorithm>
#include <array>
namespace dingosdk::launcher_start_maps {
namespace {
bool same(std::string_view a, std::string_view b) {
const auto fold = [](char c) { return c >= 'A' && c <= 'Z' ? static_cast<char>(c + ('a' - 'A')) : c; };
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [&](char x, char y) { return fold(x) == fold(y); });
}
// The retail maps, as a dedicated server lists them (server_config.cpp).
constexpr std::array<std::string_view, 6> retail{
san_vansterdam,
"Levels/Game/DingoLevel_Isle_of_Grom/DingoLevel_Isle_of_Grom",
"Levels/Game/DingoLevel_MPR/DingoLevel_MPR",
"Levels/Game/DingoLevel_FTUE_Island/DingoLevel_FTUE_Island",
"Levels/Game/DingoLevel_SDM/DingoLevel_SDM_Int_001/DingoLevel_SDM_Int_001",
"Levels/Game/DingoLevel_SDM/DingoLevel_SDM_Int_002/DingoLevel_SDM_Int_002"};
} // namespace
std::vector<StartMap> start_maps(const std::filesystem::path& game_directory) {
std::vector<StartMap> maps;
for (const auto asset : retail) maps.push_back({std::string(asset), world_level_name(asset), {}, {}});
// The mods the game's level list reads (mod_catalog.cpp, level_manifest_paths): switched on,
// not left out by the last merge, and made for this game build.
const auto list = mods::scan_mods(game_directory);
if (!list.issue.empty()) return maps;
for (const auto& entry : list.entries) {
const auto& mod = entry.mod;
if (!entry.enabled || !mod.provides_levels || !mod.outdated.empty() || list.excluded.contains(mod.name)) continue;
const auto manifest = read_custom_level_manifest(mod.directory / "reskate-levels.json");
if (!manifest.issue.empty()) continue;
for (const auto& level : manifest.levels) {
if (find_start_map(maps, level.asset)) continue;
StartMap map{level.asset, level.display_name, mod.name, {}};
// A map with one start point starts there; one with several must be given one.
if (level.start_points.size() > 1 && level.manifest_start_point.empty())
map.start_point = level.start_points.front().name;
maps.push_back(std::move(map));
}
}
return maps;
}
const StartMap* find_start_map(const std::vector<StartMap>& maps, std::string_view asset) {
for (const auto& map : maps)
if (same(map.asset, asset)) return &map;
return nullptr;
}
std::wstring load_arguments(const StartMap& map) {
if (same(map.asset, san_vansterdam)) return {};
// Asset and start point names are plain ASCII (custom_level_manifest.cpp); a start point with
// a space in it is quoted, as the console reads it.
std::wstring text(map.asset.begin(), map.asset.end());
if (!map.start_point.empty()) {
const bool spaced = map.start_point.find(' ') != std::string::npos;
text += spaced ? L" '" : L" ";
text.append(map.start_point.begin(), map.start_point.end());
if (spaced) text += L"'";
}
return text;
}
} // namespace dingosdk::launcher_start_maps
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
// The map the game starts in (Settings > GAME > Start map). The game always starts in San
// Vansterdam; another map is loaded once it is up, as the console's "load" would (the launcher's
// -map, launch.cpp), so San Vansterdam is chosen by asking for nothing.
namespace dingosdk::launcher_start_maps {
inline constexpr std::string_view san_vansterdam = "Levels/Game/BAM_LevelRoot/BAM_LevelRoot";
struct StartMap {
std::string asset; // the level, as reskate-levels.json or the game names it
std::string name; // as the game's menus show it
std::string mod; // the mod folder that adds it; empty for one of the game's own
std::string start_point; // its first, for a custom map with several; empty: the map's own choice
};
// San Vansterdam first, the game's other maps, then the custom maps of the mods in
// `game_directory`\Mods that load (switched on, merged, made for this game build), in load order.
std::vector<StartMap> start_maps(const std::filesystem::path& game_directory);
// The map with this asset (ASCII case ignored), or null.
const StartMap* find_start_map(const std::vector<StartMap>& maps, std::string_view asset);
// What follows "load " for the map: its asset, and its start point when it has several and names
// none to start at. Empty for San Vansterdam, which the game is in already.
std::wstring load_arguments(const StartMap& map);
} // namespace dingosdk::launcher_start_maps
+1 -1
View File
@@ -248,7 +248,7 @@ Useful launcher flags:
| `--log-trace` | the same as `--log-level=trace`; an explicit `--log-level` wins |
| `-wconsole` | also show the log live in a console window while the game runs |
| `--menu-key=0x2D`, `--console-key=0xC0` | menu and console keys (virtual-key codes) |
| `-map <name>` | load that map as soon as the game is up, e.g. `-map "San Vansterdam"`; the name is a map's name, short name or asset path, or the start of one |
| `-map <name>` | load that map as soon as the game is up, e.g. `-map "San Vansterdam"`; the name is a map's name, short name or asset path, or the start of one. Over the start map chosen in Settings > GAME |
| `--no-loose-files` | ignore loose Lua and config files beside the game |
| `--gpu-diagnostics` | record extra detail when the graphics driver crashes (DRED) |
+17 -1
View File
@@ -583,7 +583,9 @@ void update_model(std::uintptr_t client, TickState& frame) {
// RESKATE_STARTUP_COMMANDS="load skate1map;noclip 1" (development, and the launcher's
// -map <name>, which is a "load" at the front of it): console commands
// queued in order once the world is up and can accept them, so a build can be exercised
// without anyone driving the overlay. A load waits for its destination to be listed.
// without anyone driving the overlay. A load waits for its destination to be listed, for 20
// seconds once the world can load (the launcher's start map, start_maps.h, falls back to the
// map the game opened in).
{
static std::optional<std::vector<std::string>> startup_commands;
if (!startup_commands) {
@@ -624,12 +626,26 @@ void update_model(std::uintptr_t client, TickState& frame) {
}
}
bool ready = !wait && r.model.can_queue_load && !r.requests.loading();
// A destination still not listed this long after the world could load it is not
// installed (a map mod switched off or left out of the merge): the load is dropped and
// the game stays where it started, instead of the list waiting on it for ever.
static std::optional<std::chrono::steady_clock::time_point> missing_since;
if (ready && lower(text).starts_with("load ")) {
const auto wanted = lower(text.substr(5, text.find(' ', 5) == std::string::npos ? std::string::npos : text.find(' ', 5) - 5));
ready = std::ranges::any_of(r.model.levels, [&](const dingosdk::overlay::Level& level) {
return level.can_load && (lower(level.asset).find(wanted) != std::string::npos ||
lower(level.display_name) == wanted);
});
const auto now = std::chrono::steady_clock::now();
if (ready) missing_since.reset();
else if (!missing_since) missing_since = now;
else if (now - *missing_since >= std::chrono::seconds(20)) {
dingosdk::logging::log(dingosdk::logging::Level::warning, dingosdk::logging::Channel::level,
"Startup command dropped: {} is not an installed map (is its mod switched off?); staying in this map.",
text.substr(5));
missing_since.reset();
startup_commands->erase(startup_commands->begin());
}
}
if (ready && r.requests.enqueue(ConsoleRequest{text}, request_context(r), GetCurrentThreadId())) {
dingosdk::logging::log(dingosdk::logging::Level::info, dingosdk::logging::Channel::level,
+6 -2
View File
@@ -4,9 +4,9 @@ if(WIN32)
Launcher/gui_settings.cpp Launcher/gui_sign_in.cpp Launcher/gui_mods.cpp Launcher/gui_mods_browse.cpp Launcher/gui_mods_profiles.cpp
Launcher/gui_gamepad.cpp Launcher/gamepad_input.cpp Launcher/gui_changelog.cpp Launcher/gui_legal.cpp Launcher/game_settings.cpp
Launcher/updater.cpp Launcher/mod_manager.cpp Launcher/mod_profiles.cpp Launcher/thunderstore.cpp
Launcher/problem.h)
Launcher/start_maps.cpp Launcher/problem.h)
target_link_libraries(dingosdk_launcher PRIVATE dingosdk_logging dingosdk_content_cache_install dingosdk_world_layer_scan dingosdk_launcher_support dingosdk_initfs
dingosdk_mod_list dingosdk_mods dingosdk_json dingosdk_miniz dingosdk_imgui dingosdk_playstation_input winhttp shell32 dwmapi windowscodecs ole32)
dingosdk_mod_list dingosdk_custom_level_manifest dingosdk_mods dingosdk_json dingosdk_miniz dingosdk_imgui dingosdk_playstation_input winhttp shell32 dwmapi windowscodecs ole32)
set_target_properties(dingosdk_launcher PROPERTIES OUTPUT_NAME "ReSkateLauncher")
dingosdk_version_info(dingosdk_launcher "ReSkate launcher" "ReSkateLauncher.exe" VFT_APP)
endif()
@@ -40,6 +40,10 @@ if(DINGOSDK_BUILD_LAUNCHER_TESTS AND WIN32)
target_link_libraries(dingosdk_thunderstore_package_tests PRIVATE dingosdk_json)
target_include_directories(dingosdk_thunderstore_package_tests PRIVATE "${PROJECT_SOURCE_DIR}")
add_test(NAME thunderstore_package COMMAND dingosdk_thunderstore_package_tests)
add_executable(dingosdk_start_maps_tests Launcher/Test/start_maps_tests.cpp Launcher/start_maps.cpp)
target_link_libraries(dingosdk_start_maps_tests PRIVATE dingosdk_mod_list dingosdk_custom_level_manifest)
target_include_directories(dingosdk_start_maps_tests PRIVATE "${PROJECT_SOURCE_DIR}")
add_test(NAME launcher_start_maps COMMAND dingosdk_start_maps_tests "${CMAKE_CURRENT_BINARY_DIR}/start-maps-fixture")
add_executable(dingosdk_game_settings_tests Launcher/Test/game_settings_tests.cpp Launcher/game_settings.cpp)
target_include_directories(dingosdk_game_settings_tests PRIVATE "${PROJECT_SOURCE_DIR}")
add_test(NAME launcher_game_settings COMMAND dingosdk_game_settings_tests)