1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Game configuration as stored in registry */
18
19#include <C4Config.h>
20
21#include "C4Version.h"
22#ifdef C4ENGINE
23#include <C4Application.h>
24#include "C4GameControl.h"
25#include <C4Log.h>
26#include <C4Network2.h>
27#include "C4Network2IO.h"
28#include "C4Network2Reference.h"
29#include "C4Network2UPnP.h"
30#include "C4Record.h"
31#include "C4ResStrTable.h"
32#include <C4UpperBoard.h>
33#include "StdPNG.h"
34#endif
35
36#include <StdFile.h>
37
38#ifdef _WIN32
39#include "StdRegistry.h"
40#elif defined(__linux__)
41#include <clocale>
42#endif
43
44#include <format>
45
46bool isGermanSystem()
47{
48#ifdef _WIN32
49 if (PRIMARYLANGID(GetUserDefaultLangID()) == LANG_GERMAN) return true;
50#elif defined(__APPLE__) and defined(C4ENGINE)
51 extern bool isGerman();
52 if (isGerman()) return true;
53#elif defined(__linux__)
54 if (strstr(haystack: std::setlocale(LC_MESSAGES, locale: nullptr), needle: "de")) return true;
55#endif
56 return false;
57}
58
59void C4ConfigGeneral::CompileFunc(StdCompiler *pComp)
60{
61 // For those without the ability to intuitively guess what the falses and trues mean:
62 // its mkNamingAdapt(field, name, default, fPrefillDefault, fStoreDefault)
63 // where fStoreDefault writes out the value to the config even if it's the same as the default.
64#define s mkStringAdaptM
65 // Version got introduced in 348, so any config without it is assumed to be created by 347
66 pComp->Value(rStruct: mkNamingAdapt(rValue&: Version, szName: "Version", rDefault: 347));
67 pComp->Value(rStruct: mkNamingAdapt(s(Name), szName: "Name", rDefault: ""));
68 pComp->Value(rStruct: mkNamingAdapt(s(Language), szName: "Language", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
69 pComp->Value(rStruct: mkNamingAdapt(s(LanguageEx), szName: "LanguageEx", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
70 pComp->Value(rStruct: mkNamingAdapt(s(LanguageCharset), szName: "LanguageCharset", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
71 fUTF8 = SEqual(szStr1: LanguageCharset, szStr2: "UTF-8");
72 pComp->Value(rStruct: mkNamingAdapt(s(Definitions), szName: "Definitions", rDefault: ""));
73 pComp->Value(rStruct: mkNamingAdapt(s(Participants), szName: "Participants", rDefault: ""));
74 pComp->Value(rStruct: mkNamingAdapt(s(LogPath), szName: "LogPath", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
75 pComp->Value(rStruct: mkNamingAdapt(s(PlayerPath), szName: "PlayerPath", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
76 pComp->Value(rStruct: mkNamingAdapt(s(DefinitionPath), szName: "DefinitionPath", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
77#ifdef _WIN32
78 pComp->Value(mkNamingAdapt(s(UserPath), "UserPath", "%APPDATA%\\LegacyClonk", false, true));
79#elif defined(__linux__)
80 pComp->Value(rStruct: mkNamingAdapt(s(UserPath), szName: "UserPath", rDefault: "$HOME/.legacyclonk", fPrefillDefault: false, fStoreDefault: true));
81#elif defined(__APPLE__)
82 pComp->Value(mkNamingAdapt(s(UserPath), "UserPath", "$HOME/Library/Application Support/LegacyClonk", false, true));
83#endif
84 pComp->Value(rStruct: mkNamingAdapt(rValue&: SaveGameFolder, szName: "SaveGameFolder", rDefault: "Savegames.c4f", fPrefillDefault: false, fStoreDefault: true));
85 pComp->Value(rStruct: mkNamingAdapt(rValue&: SaveDemoFolder, szName: "SaveDemoFolder", rDefault: "Records.c4f", fPrefillDefault: false, fStoreDefault: true));
86#ifdef C4ENGINE
87 pComp->Value(rStruct: mkNamingAdapt(s(MissionAccess), szName: "MissionAccess", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
88#endif
89 pComp->Value(rStruct: mkNamingAdapt(rValue&: FPS, szName: "FPS", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
90 pComp->Value(rStruct: mkNamingAdapt(rValue&: Record, szName: "Record", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
91 pComp->Value(rStruct: mkNamingAdapt(rValue&: ScreenshotFolder, szName: "ScreenshotFolder", rDefault: "Screenshots", fPrefillDefault: false, fStoreDefault: true));
92 pComp->Value(rStruct: mkNamingAdapt(rValue&: FairCrew, szName: "NoCrew", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
93 pComp->Value(rStruct: mkNamingAdapt(rValue&: FairCrewStrength, szName: "DefCrewStrength", rDefault: 1000, fPrefillDefault: false, fStoreDefault: true));
94 pComp->Value(rStruct: mkNamingAdapt(rValue&: ScrollSmooth, szName: "ScrollSmooth", rDefault: 4));
95 pComp->Value(rStruct: mkNamingAdapt(rValue&: AlwaysDebug, szName: "DebugMode", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
96 pComp->Value(rStruct: mkNamingAdapt(rValue&: AllowScriptingInReplays, szName: "AllowScriptingInReplays", rDefault: false));
97
98 pComp->Value(rStruct: mkNamingAdapt(s(RXFontName), szName: "FontName", rDefault: "Endeavour", fPrefillDefault: false, fStoreDefault: true));
99 pComp->Value(rStruct: mkNamingAdapt(rValue&: RXFontSize, szName: "FontSize", rDefault: 14, fPrefillDefault: false, fStoreDefault: true));
100 pComp->Value(rStruct: mkNamingAdapt(rValue&: GamepadEnabled, szName: "GamepadEnabled", rDefault: true));
101 pComp->Value(rStruct: mkNamingAdapt(rValue&: FirstStart, szName: "FirstStart", rDefault: true));
102 pComp->Value(rStruct: mkNamingAdapt(rValue&: UserPortraitsWritten, szName: "UserPortraitsWritten", rDefault: false));
103 pComp->Value(rStruct: mkNamingAdapt(rValue&: ConfigResetSafety, szName: "ConfigResetSafety", rDefault: static_cast<int32_t>(ConfigResetSafetyVal)));
104 pComp->Value(rStruct: mkNamingAdapt(rValue&: UseWhiteIngameChat, szName: "UseWhiteIngameChat", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
105 pComp->Value(rStruct: mkNamingAdapt(rValue&: UseWhiteLobbyChat, szName: "UseWhiteLobbyChat", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
106 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowLogTimestamps, szName: "ShowLogTimestamps", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
107
108#ifdef __APPLE__
109 pComp->Value(mkNamingAdapt(Preloading, "Preloading", false));
110#else
111 pComp->Value(rStruct: mkNamingAdapt(rValue&: Preloading, szName: "Preloading", rDefault: true));
112#endif
113
114#ifndef _WIN32
115 pComp->Value(rStruct: mkNamingAdapt(rValue&: ThreadPoolThreadCount, szName: "ThreadPoolThreadCount", rDefault: 8));
116#endif
117}
118
119#ifdef C4ENGINE
120
121void C4ConfigDeveloper::ConsoleScriptStrictnessWrapper::CompileFunc(StdCompiler *const comp)
122{
123 StdEnumEntry<C4AulScriptStrict> ConsoleScriptStrictnessValues[] =
124 {
125 {.Name: "NonStrict", .Val: C4AulScriptStrict::NONSTRICT},
126 {.Name: "Strict1", .Val: C4AulScriptStrict::STRICT1},
127 {.Name: "Strict2", .Val: C4AulScriptStrict::STRICT2},
128 {.Name: "Strict3", .Val: C4AulScriptStrict::STRICT3},
129 {.Name: "MaxStrict", .Val: MaxStrictSentinel}
130 };
131
132 comp->Value(rStruct: mkEnumAdaptT<C4AulScriptStrict>(rVal&: Strictness, pNames: ConsoleScriptStrictnessValues));
133
134 if (comp->isCompiler() && Strictness != MaxStrictSentinel)
135 {
136 Strictness = static_cast<C4AulScriptStrict>(std::clamp(val: std::to_underlying(value: Strictness), lo: std::to_underlying(value: C4AulScriptStrict::NONSTRICT), hi: std::to_underlying(value: C4AulScriptStrict::MAXSTRICT)));
137 }
138}
139
140void C4ConfigDeveloper::CompileFunc(StdCompiler *pComp)
141{
142 pComp->Value(rStruct: mkNamingAdapt(rValue&: AutoFileReload, szName: "AutoFileReload", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
143 pComp->Value(rStruct: mkNamingAdapt(rValue&: ConsoleScriptStrictness, szName: "ConsoleScriptStrictness", rDefault: ConsoleScriptStrictnessWrapper{.Strictness: ConsoleScriptStrictnessWrapper::MaxStrictSentinel}));
144}
145
146void C4ConfigGraphics::CompileFunc(StdCompiler *pComp)
147{
148 pComp->Value(rStruct: mkNamingAdapt(rValue&: ResX, szName: "ResolutionX", rDefault: 800, fPrefillDefault: false, fStoreDefault: true));
149 pComp->Value(rStruct: mkNamingAdapt(rValue&: ResY, szName: "ResolutionY", rDefault: 600, fPrefillDefault: false, fStoreDefault: true));
150 pComp->Value(rStruct: mkNamingAdapt(rValue&: Scale, szName: "Scale", rDefault: 100, fPrefillDefault: false, fStoreDefault: true));
151 pComp->Default(szName: "ShowAllResolutions");
152 pComp->Value(rStruct: mkNamingAdapt(rValue&: SplitscreenDividers, szName: "SplitscreenDividers", rDefault: 1));
153 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowPlayerHUDAlways, szName: "ShowPlayerHUDAlways", rDefault: true));
154 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowPortraits, szName: "ShowPortraits", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
155 pComp->Value(rStruct: mkNamingAdapt(rValue&: AddNewCrewPortraits, szName: "AddNewCrewPortraits", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
156 pComp->Value(rStruct: mkNamingAdapt(rValue&: SaveDefaultPortraits, szName: "SaveDefaultPortraits", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
157 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowCommands, szName: "ShowCommands", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
158 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowCommandKeys, szName: "ShowCommandKeys", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
159 pComp->Value(rStruct: mkNamingAdapt(rValue&: ColorAnimation, szName: "ColorAnimation", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
160 pComp->Value(rStruct: mkNamingAdapt(rValue&: SmokeLevel, szName: "SmokeLevel", rDefault: 200, fPrefillDefault: false, fStoreDefault: true));
161 pComp->Value(rStruct: mkNamingAdapt(rValue&: VerboseObjectLoading, szName: "VerboseObjectLoading", rDefault: 0, fPrefillDefault: false, fStoreDefault: true));
162
163 StdEnumEntry<int32_t> UpperBoardDisplayModes[] =
164 {
165 {.Name: "Hide", .Val: C4UpperBoard::Hide},
166 {.Name: "Full", .Val: C4UpperBoard::Full},
167 {.Name: "Small", .Val: C4UpperBoard::Small},
168 {.Name: "Mini", .Val: C4UpperBoard::Mini}
169 };
170 pComp->Value(rStruct: mkNamingAdapt(rValue: mkEnumAdaptT<int32_t>(rVal&: UpperBoard, pNames: UpperBoardDisplayModes), szName: "UpperBoard", rDefault: C4UpperBoard::Full, fPrefillDefault: false, fStoreDefault: true));
171
172 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowClock, szName: "ShowClock", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
173 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowCrewNames, szName: "ShowCrewNames", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
174 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowCrewCNames, szName: "ShowCrewCNames", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
175 pComp->Value(rStruct: mkNamingAdapt(rValue&: MsgBoard, szName: "MsgBoard", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
176 pComp->Value(rStruct: mkNamingAdapt(rValue&: PXSGfx, szName: "PXSGfx", rDefault: true));
177 pComp->Value(rStruct: mkNamingAdapt(rValue&: Engine, szName: "Engine", GFXENGN_OPENGL, fPrefillDefault: false, fStoreDefault: true));
178 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoAlphaAdd, szName: "NoAlphaAdd", rDefault: false));
179 pComp->Value(rStruct: mkNamingAdapt(rValue&: PointFiltering, szName: "PointFiltering", rDefault: false));
180 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoBoxFades, szName: "NoBoxFades", rDefault: false));
181 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoAcceleration, szName: "NoAcceleration", rDefault: false));
182 pComp->Value(rStruct: mkNamingAdapt(rValue&: TexIndent, szName: "TexIndent", rDefault: 0));
183 pComp->Value(rStruct: mkNamingAdapt(rValue&: BlitOffset, szName: "BlitOffset", rDefault: 0));
184 pComp->Value(rStruct: mkNamingAdapt(rValue&: AllowedBlitModes, szName: "AllowedBlitModes", C4GFXBLIT_ALL));
185 pComp->Value(rStruct: mkNamingAdapt(rValue&: Gamma1, szName: "Gamma1", rDefault: 0));
186 pComp->Value(rStruct: mkNamingAdapt(rValue&: Gamma2, szName: "Gamma2", rDefault: 0x808080));
187 pComp->Value(rStruct: mkNamingAdapt(rValue&: Gamma3, szName: "Gamma3", rDefault: 0xffffff));
188 pComp->Default(szName: "Currency");
189 pComp->Value(rStruct: mkNamingAdapt(rValue&: RenderInactive, szName: "RenderInactive", rDefault: Console));
190 pComp->Value(rStruct: mkNamingAdapt(rValue&: DisableGamma, szName: "DisableGamma", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
191 pComp->Value(rStruct: mkNamingAdapt(rValue&: Monitor, szName: "Monitor", rDefault: 0)); // 0 = D3DADAPTER_DEFAULT
192 pComp->Value(rStruct: mkNamingAdapt(rValue&: FireParticles, szName: "FireParticles", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
193 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxRefreshDelay, szName: "MaxRefreshDelay", rDefault: 30));
194 pComp->Value(rStruct: mkNamingAdapt(rValue&: Shader, szName: "Shader", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
195 pComp->Value(rStruct: mkNamingAdapt(rValue&: AutoFrameSkip, szName: "AutoFrameSkip", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
196 pComp->Value(rStruct: mkNamingAdapt(rValue&: CacheTexturesInRAM, szName: "CacheTexturesInRAM", rDefault: 100));
197
198 StdEnumEntry<DisplayMode> DisplayModes[] =
199 {
200 {.Name: "Fullscreen", .Val: DisplayMode::Fullscreen},
201 {.Name: "Window", .Val: DisplayMode::Window}
202 };
203 pComp->Value(rStruct: mkNamingAdapt(rValue: mkEnumAdaptT<int>(rVal&: UseDisplayMode, pNames: DisplayModes), szName: "DisplayMode", rDefault: DisplayMode::Fullscreen, fPrefillDefault: false, fStoreDefault: true));
204
205#ifdef _WIN32
206 pComp->Value(mkNamingAdapt(Maximized, "Maximized", false, false, true));
207 pComp->Value(mkNamingAdapt(PositionX, "PositionX", 0, false, true));
208 pComp->Value(mkNamingAdapt(PositionY, "PositionY", 0, false, true));
209#endif
210
211 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShowFolderMaps, szName: "ShowFolderMaps", rDefault: true));
212 pComp->Value(rStruct: mkNamingAdapt(rValue&: UseShaderGamma, szName: "UseShaderGamma", rDefault: true));
213}
214
215void C4ConfigSound::CompileFunc(StdCompiler *pComp)
216{
217 pComp->Value(rStruct: mkNamingAdapt(rValue&: RXSound, szName: "Sound", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
218 pComp->Value(rStruct: mkNamingAdapt(rValue&: RXMusic, szName: "Music", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
219 pComp->Value(rStruct: mkNamingAdapt(rValue&: FEMusic, szName: "MenuMusic", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
220 pComp->Value(rStruct: mkNamingAdapt(rValue&: FESamples, szName: "MenuSound", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
221 pComp->Default(szName: "Verbose");
222 pComp->Value(rStruct: mkNamingAdapt(rValue&: MusicVolume, szName: "MusicVolume", rDefault: 100, fPrefillDefault: false, fStoreDefault: true));
223 pComp->Value(rStruct: mkNamingAdapt(rValue&: SoundVolume, szName: "SoundVolume", rDefault: 100, fPrefillDefault: false, fStoreDefault: true));
224 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxChannels, szName: "MaxChannels", rDefault: C4AudioSystem::MaxChannels));
225 pComp->Value(rStruct: mkNamingAdapt(rValue&: PreferLinearResampling, szName: "PreferLinearResampling", rDefault: false));
226
227 if (pComp->isCompiler())
228 {
229 MaxChannels = std::clamp(val: MaxChannels, lo: 1, hi: C4AudioSystem::MaxChannels);
230 }
231
232 pComp->Value(rStruct: mkNamingAdapt(rValue&: MuteSoundCommand, szName: "MuteSoundCommand", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
233}
234
235void C4ConfigNetwork::CompileFunc(StdCompiler *pComp)
236{
237 pComp->Value(rStruct: mkNamingAdapt(rValue&: ControlRate, szName: "ControlRate", rDefault: 2, fPrefillDefault: false, fStoreDefault: true));
238 pComp->Value(rStruct: mkNamingAdapt(s(WorkPath), szName: "WorkPath", rDefault: "Network", fPrefillDefault: false, fStoreDefault: true));
239 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoRuntimeJoin, szName: "NoRuntimeJoin", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
240 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxResSearchRecursion, szName: "MaxResSearchRecursion", rDefault: 1, fPrefillDefault: false, fStoreDefault: true));
241 pComp->Value(rStruct: mkNamingAdapt(rValue&: Comment, szName: "Comment", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
242
243 pComp->Value(rStruct: mkNamingAdapt(rValue&: PortTCP, szName: "PortTCP", rDefault: C4NetStdPortTCP, fPrefillDefault: false, fStoreDefault: true));
244 pComp->Value(rStruct: mkNamingAdapt(rValue&: PortUDP, szName: "PortUDP", rDefault: C4NetStdPortUDP, fPrefillDefault: false, fStoreDefault: true));
245 pComp->Value(rStruct: mkNamingAdapt(rValue&: PortDiscovery, szName: "PortDiscovery", rDefault: C4NetStdPortDiscovery, fPrefillDefault: false, fStoreDefault: true));
246 pComp->Value(rStruct: mkNamingAdapt(rValue&: PortRefServer, szName: "PortRefServer", rDefault: C4NetStdPortRefServer, fPrefillDefault: false, fStoreDefault: true));
247
248 pComp->Value(rStruct: mkNamingAdapt(rValue&: ControlMode, szName: "ControlMode", rDefault: 0, fPrefillDefault: false, fStoreDefault: true));
249 pComp->Value(rStruct: mkNamingAdapt(rValue&: LocalName, szName: "LocalName", rDefault: "Unknown", fPrefillDefault: false, fStoreDefault: true));
250 pComp->Value(rStruct: mkNamingAdapt(rValue&: Nick, szName: "Nick", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
251 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxLoadFileSize, szName: "MaxLoadFileSize", rDefault: 100 * 1024 * 1024, fPrefillDefault: false, fStoreDefault: true));
252
253 pComp->Value(rStruct: mkNamingAdapt(rValue&: MasterServerSignUp, szName: "MasterServerSignUp", rDefault: true, fPrefillDefault: false, fStoreDefault: true));
254 pComp->Value(rStruct: mkNamingAdapt(rValue&: MasterReferencePeriod, szName: "MasterReferencePeriod", rDefault: 120, fPrefillDefault: false, fStoreDefault: true));
255 pComp->Value(rStruct: mkNamingAdapt(rValue&: LeagueServerSignUp, szName: "LeagueServerSignUp", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
256 pComp->Value(rStruct: mkNamingAdapt(s(ServerAddress), szName: "ServerAddress", C4CFG_LeagueServer, fPrefillDefault: false, fStoreDefault: true));
257 pComp->Value(rStruct: mkNamingAdapt(rValue&: UseAlternateServer, szName: "UseAlternateServer", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
258 pComp->Value(rStruct: mkNamingAdapt(s(AlternateServerAddress), szName: "AlternateServerAddress", C4CFG_LeagueServer, fPrefillDefault: false, fStoreDefault: true));
259 pComp->Value(rStruct: mkNamingAdapt(s(UpdateServerAddress), szName: "UpdateServerAddress", C4CFG_UpdateServer));
260 pComp->Value(rStruct: mkNamingAdapt(s(LastPassword), szName: "LastPassword", rDefault: "Wipf", fPrefillDefault: false, fStoreDefault: true));
261 pComp->Value(rStruct: mkNamingAdapt(rValue&: AutomaticUpdate, szName: "EnableAutomaticUpdate", rDefault: true));
262 pComp->Value(rStruct: mkNamingAdapt(rValue&: LastUpdateTime, szName: "LastUpdateTime", rDefault: 0, fPrefillDefault: false, fStoreDefault: true));
263 pComp->Value(rStruct: mkNamingAdapt(rValue&: AsyncMaxWait, szName: "AsyncMaxWait", rDefault: 2, fPrefillDefault: false, fStoreDefault: true));
264
265 pComp->Value(rStruct: mkNamingAdapt(s(PuncherAddress), szName: "PuncherAddress", rDefault: DefaultPuncherServer, fPrefillDefault: false, fStoreDefault: true));
266
267 pComp->Value(rStruct: mkNamingAdapt(rValue&: LeagueAccount, szName: "LeagueNick", rDefault: "", fPrefillDefault: false, fStoreDefault: false));
268 pComp->Value(rStruct: mkNamingAdapt(rValue&: LeagueAutoLogin, szName: "LeagueAutoLogin", rDefault: true, fPrefillDefault: false, fStoreDefault: false));
269 pComp->Value(rStruct: mkNamingAdapt(rValue&: UseCurl, szName: "UseCurl", rDefault: true));
270 pComp->Value(rStruct: mkNamingAdapt(rValue&: EnableUPnP, szName: "EnableUPnP", rDefault: true));
271}
272
273void C4ConfigLobby::CompileFunc(StdCompiler *pComp)
274{
275 pComp->Value(rStruct: mkNamingAdapt(rValue&: AllowPlayerSave, szName: "AllowPlayerSave", rDefault: false, fPrefillDefault: false, fStoreDefault: true));
276 pComp->Value(rStruct: mkNamingAdapt(rValue&: CountdownTime, szName: "CountdownTime", rDefault: 5, fPrefillDefault: false, fStoreDefault: true));
277}
278
279void C4ConfigIRC::CompileFunc(StdCompiler *pComp)
280{
281 pComp->Value(rStruct: mkNamingAdapt(s(Server), szName: "Server2", rDefault: "irc.euirc.net", fPrefillDefault: false, fStoreDefault: true));
282 pComp->Value(rStruct: mkNamingAdapt(s(Nick), szName: "Nick", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
283 pComp->Value(rStruct: mkNamingAdapt(s(RealName), szName: "RealName", rDefault: "", fPrefillDefault: false, fStoreDefault: true));
284 pComp->Value(rStruct: mkNamingAdapt(s(Channel), szName: "Channel", rDefault: "#clonken,#legacyclonk", fPrefillDefault: false, fStoreDefault: true));
285}
286
287void C4ConfigGamepad::CompileFunc(StdCompiler *pComp, bool fButtonsOnly)
288{
289 /* The defaults here are for a Logitech Dual Action under Linux-SDL. Better than nothing, I guess. */
290 if (!fButtonsOnly)
291 {
292 for (int i = 0; i < 6; ++i)
293 {
294 pComp->Value(rStruct: mkNamingAdapt(rValue&: AxisMin[i], szName: std::format(fmt: "Axis{}Min", args&: i).c_str(), rDefault: 0u));
295 pComp->Value(rStruct: mkNamingAdapt(rValue&: AxisMax[i], szName: std::format(fmt: "Axis{}Max", args&: i).c_str(), rDefault: 0u));
296 pComp->Value(rStruct: mkNamingAdapt(rValue&: AxisCalibrated[i], szName: std::format(fmt: "Axis{}Calibrated", args&: i).c_str(), rDefault: false));
297 }
298 }
299 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[0], szName: "Button1", rDefault: -1));
300 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[1], szName: "Button2", rDefault: -1));
301 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[2], szName: "Button3", rDefault: -1));
302 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[3], szName: "Button4", rDefault: -1));
303 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[4], szName: "Button5", rDefault: -1));
304 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[5], szName: "Button6", rDefault: -1));
305 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[6], szName: "Button7", rDefault: -1));
306 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[7], szName: "Button8", rDefault: -1));
307 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[8], szName: "Button9", rDefault: -1));
308 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[9], szName: "Button10", rDefault: -1));
309 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[10], szName: "Button11", rDefault: -1));
310 pComp->Value(rStruct: mkNamingAdapt(rValue&: Button[11], szName: "Button12", rDefault: -1));
311}
312
313void C4ConfigGamepad::Reset()
314{
315 // loads an empty config for the gamepad config
316 StdCompilerNull Comp; Comp.Compile(rStruct: mkParAdapt(rObj&: *this, rPar: false));
317}
318
319void C4ConfigControls::CompileFunc(StdCompiler *pComp, bool fKeysOnly)
320{
321#ifndef USE_CONSOLE
322#ifdef _WIN32
323#define KEY(win, x, sdl) win
324#elif defined(USE_X11)
325#define KEY(win, x, sdl) x
326#else
327#define KEY(win, x, sdl) sdl
328#endif
329
330 bool fGer = isGermanSystem();
331
332 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 0], szName: "Kbd1Key1", KEY('Q', XK_q, SDL_SCANCODE_Q)));
333 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 1], szName: "Kbd1Key2", KEY('W', XK_w, SDL_SCANCODE_W)));
334 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 2], szName: "Kbd1Key3", KEY('E', XK_e, SDL_SCANCODE_E)));
335 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 3], szName: "Kbd1Key4", KEY('A', XK_a, SDL_SCANCODE_A)));
336 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 4], szName: "Kbd1Key5", KEY('S', XK_s, SDL_SCANCODE_S)));
337 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 5], szName: "Kbd1Key6", KEY('D', XK_d, SDL_SCANCODE_D)));
338 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 6], szName: "Kbd1Key7", rDefault: fGer ? KEY('Y', XK_y, SDL_SCANCODE_Z) : KEY('Z', XK_z, SDL_SCANCODE_Z)));
339 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 7], szName: "Kbd1Key8", KEY('X', XK_x, SDL_SCANCODE_X)));
340 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 8], szName: "Kbd1Key9", KEY('C', XK_c, SDL_SCANCODE_C)));
341 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][ 9], szName: "Kbd1Key10", rDefault: fGer ? KEY(226, XK_less, SDL_SCANCODE_NONUSBACKSLASH) : KEY('R', XK_r, SDL_SCANCODE_R)));
342 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][10], szName: "Kbd1Key11", KEY('V', XK_v, SDL_SCANCODE_V)));
343 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[0][11], szName: "Kbd1Key12", KEY('F', XK_f, SDL_SCANCODE_F)));
344
345 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 0], szName: "Kbd2Key1", KEY(103, XK_KP_Home, SDL_SCANCODE_KP_7)));
346 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 1], szName: "Kbd2Key2", KEY(104, XK_KP_Up, SDL_SCANCODE_KP_8)));
347 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 2], szName: "Kbd2Key3", KEY(105, XK_KP_Page_Up, SDL_SCANCODE_KP_9)));
348 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 3], szName: "Kbd2Key4", KEY(100, XK_KP_Left, SDL_SCANCODE_KP_4)));
349 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 4], szName: "Kbd2Key5", KEY(101, XK_KP_Begin, SDL_SCANCODE_KP_5)));
350 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 5], szName: "Kbd2Key6", KEY(102, XK_KP_Right, SDL_SCANCODE_KP_6)));
351 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 6], szName: "Kbd2Key7", KEY( 97, XK_KP_End, SDL_SCANCODE_KP_1)));
352 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 7], szName: "Kbd2Key8", KEY( 98, XK_KP_Down, SDL_SCANCODE_KP_2)));
353 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 8], szName: "Kbd2Key9", KEY( 99, XK_KP_Page_Down, SDL_SCANCODE_KP_3)));
354 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][ 9], szName: "Kbd2Key10", KEY( 96, XK_KP_Insert, SDL_SCANCODE_KP_0)));
355 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][10], szName: "Kbd2Key11", KEY(110, XK_KP_Delete, SDL_SCANCODE_KP_PERIOD)));
356 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[1][11], szName: "Kbd2Key12", KEY(107, XK_KP_Add, SDL_SCANCODE_KP_PLUS)));
357
358 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 0], szName: "Kbd3Key1", KEY('I', XK_i, SDL_SCANCODE_I)));
359 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 1], szName: "Kbd3Key2", KEY('O', XK_o, SDL_SCANCODE_O)));
360 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 2], szName: "Kbd3Key3", KEY('P', XK_p, SDL_SCANCODE_P)));
361 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 3], szName: "Kbd3Key4", KEY('K', XK_k, SDL_SCANCODE_K)));
362 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 4], szName: "Kbd3Key5", KEY('L', XK_l, SDL_SCANCODE_L)));
363 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 5], szName: "Kbd3Key6", rDefault: fGer ? KEY(192, XK_odiaeresis, SDL_SCANCODE_SEMICOLON) : KEY(0xBA, XK_semicolon, SDL_SCANCODE_SEMICOLON)));
364 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 6], szName: "Kbd3Key7", KEY(188, XK_comma, SDL_SCANCODE_COMMA)));
365 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 7], szName: "Kbd3Key8", KEY(190, XK_period, SDL_SCANCODE_PERIOD)));
366 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 8], szName: "Kbd3Key9", rDefault: fGer ? KEY(189, XK_minus, SDL_SCANCODE_SLASH) : KEY(0xBF, XK_slash, SDL_SCANCODE_SLASH)));
367 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][ 9], szName: "Kbd3Key10", KEY('M', XK_m, SDL_SCANCODE_M)));
368 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][10], szName: "Kbd3Key11", KEY(222, XK_adiaeresis, SDL_SCANCODE_APOSTROPHE)));
369 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[2][11], szName: "Kbd3Key12", KEY(186, XK_udiaeresis, SDL_SCANCODE_LEFTBRACKET)));
370
371 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 0], szName: "Kbd4Key1", KEY(VK_INSERT, XK_Insert, SDL_SCANCODE_INSERT)));
372 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 1], szName: "Kbd4Key2", KEY(VK_HOME, XK_Home, SDL_SCANCODE_HOME)));
373 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 2], szName: "Kbd4Key3", KEY(VK_PRIOR, XK_Page_Up, SDL_SCANCODE_PAGEUP)));
374 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 3], szName: "Kbd4Key4", KEY(VK_DELETE, XK_Delete, SDL_SCANCODE_DELETE)));
375 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 4], szName: "Kbd4Key5", KEY(VK_UP, XK_Up, SDL_SCANCODE_UP)));
376 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 5], szName: "Kbd4Key6", KEY(VK_NEXT, XK_Page_Down, SDL_SCANCODE_PAGEDOWN)));
377 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 6], szName: "Kbd4Key7", KEY(VK_LEFT, XK_Left, SDL_SCANCODE_LEFT)));
378 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 7], szName: "Kbd4Key8", KEY(VK_DOWN, XK_Down, SDL_SCANCODE_DOWN)));
379 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 8], szName: "Kbd4Key9", KEY(VK_RIGHT, XK_Right, SDL_SCANCODE_RIGHT)));
380 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][ 9], szName: "Kbd4Key10", KEY(VK_END, XK_End, SDL_SCANCODE_END)));
381 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][10], szName: "Kbd4Key11", KEY(VK_RETURN, XK_Return, SDL_SCANCODE_RETURN)));
382 pComp->Value(rStruct: mkNamingAdapt(rValue&: Keyboard[3][11], szName: "Kbd4Key12", KEY(VK_BACK, XK_BackSpace, SDL_SCANCODE_BACKSPACE)));
383
384 if (fKeysOnly) return;
385
386 pComp->Value(rStruct: mkNamingAdapt(rValue&: MouseAScroll, szName: "MouseAutoScroll", rDefault: 0));
387 pComp->Value(rStruct: mkNamingAdapt(rValue&: GamepadGuiControl, szName: "GamepadGuiControl", rDefault: 0, fPrefillDefault: false, fStoreDefault: true));
388
389#undef KEY
390#undef s
391#endif // USE_CONSOLE
392}
393
394void C4ConfigCooldowns::CompileFunc(StdCompiler *comp)
395{
396 using namespace std::chrono_literals;
397
398 comp->Value(rStruct: mkNamingAdapt(rValue&: SoundCommand, szName: "SoundCommand", rDefault: 0s));
399 comp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: ReadyCheck, rPar: 5s), szName: "ReadyCheck", rDefault: 10s));
400}
401
402void C4ConfigToasts::CompileFunc(StdCompiler *comp)
403{
404 comp->Value(rStruct: mkNamingAdapt(rValue&: ReadyCheck, szName: "ReadyCheck", rDefault: true));
405}
406
407void C4ConfigLogging::CompileFunc(StdCompiler *const comp)
408{
409 comp->Value(rStruct: mkNamingAdapt(rValue&: LogLevelStdout, szName: "LogLevelStdout", rDefault: spdlog::level::info));
410
411 comp->Value(rStruct&: AudioSystem);
412 comp->Value(rStruct&: AulExec);
413 comp->Value(rStruct&: AulProfiler);
414 comp->Value(rStruct&: DDraw);
415 comp->Value(rStruct&: GameControl);
416 comp->Value(rStruct&: Network);
417 comp->Value(rStruct&: Network2IO);
418 comp->Value(rStruct&: Network2HTTPClient);
419 comp->Value(rStruct&: Network2UPnP);
420 comp->Value(rStruct&: Playback);
421 comp->Value(rStruct&: PNGFile);
422
423#ifdef WITH_GLIB
424 comp->Value(rStruct&: GLib);
425#endif
426}
427#endif
428
429C4Config::C4Config()
430{
431 Default();
432}
433
434C4Config::~C4Config()
435{
436 fConfigLoaded = false;
437}
438
439void C4Config::Default()
440{
441 // force default values
442 StdCompilerNull Comp; Comp.Compile(rStruct&: *this);
443 fConfigLoaded = false;
444}
445
446bool C4Config::Load(bool forceWorkingDirectory, const char *szConfigFile)
447{
448 try
449 {
450#ifdef _WIN32
451 // Windows: Default load from registry, if no explicit config file is specified
452 if (!szConfigFile)
453 {
454 StdCompilerConfigRead CfgRead(HKEY_CURRENT_USER, "Software\\" C4CFG_Company "\\" C4CFG_Product);
455 CfgRead.Compile(*this);
456 }
457 else
458#endif
459 {
460 // Nonwindows or explicit config file: Determine filename to load config from
461 StdStrBuf filename;
462 if (szConfigFile)
463 {
464 // Config filename is specified
465 filename.Ref(pnData: szConfigFile);
466 // make sure we're at the correct path to load it
467 if (forceWorkingDirectory) General.DeterminePaths(forceWorkingDirectory: true);
468 }
469 else
470 {
471 // Config filename from home
472 StdStrBuf home(getenv(name: "HOME"), false);
473 if (home) { home += "/"; }
474 filename.Copy(Buf2: home);
475#ifdef __APPLE__
476 filename += "Library/Preferences/legacyclonk.config";
477#else
478 filename += ".legacyclonk/config";
479#endif
480 }
481
482 // Load config file into buf
483 StdStrBuf buf;
484 buf.LoadFromFile(szFile: filename.getData());
485
486 if (buf.isNull())
487 {
488 // Config file not present?
489#ifdef __linux__
490 if (!szConfigFile)
491 {
492 StdStrBuf filename(getenv(name: "HOME"), false);
493 if (filename) { filename += "/"; }
494 filename += ".legacyclonk";
495 MakeDirectory(pathname: filename.getData());
496 }
497#endif
498 // Buggy StdCompiler crashes when compiling a Null-StdStrBuf
499 buf.Ref(pnData: " ");
500 }
501
502 // Read config from buffer
503 StdCompilerINIRead IniRead;
504 IniRead.setInput(buf);
505 IniRead.Compile(rStruct&: *this);
506 }
507 }
508 catch ([[maybe_unused]] const StdCompiler::Exception &e)
509 {
510 // Configuration file syntax error?
511#ifdef C4ENGINE
512 spdlog::critical(fmt: "Error loading configuration: {}", args: e.what());
513#endif
514 return false;
515 }
516
517 // Config postinit
518 General.DeterminePaths(forceWorkingDirectory);
519#ifdef C4ENGINE
520 AdaptToCurrentVersion();
521#ifdef _WIN32
522 bool fWinSock = AcquireWinSock();
523#endif
524 if (SEqual(szStr1: Network.LocalName.getData(), szStr2: "Unknown"))
525 {
526 char LocalName[25 + 1]; *LocalName = 0;
527 gethostname(name: LocalName, len: 25);
528 if (*LocalName) Network.LocalName.Copy(pnData: LocalName);
529 }
530#ifdef _WIN32
531 if (fWinSock) ReleaseWinSock();
532#endif
533#endif
534 General.DefaultLanguage();
535#ifdef C4ENGINE
536#ifndef USE_CONSOLE
537 if (Graphics.Engine != GFXENGN_NOGFX) Graphics.Engine = GFXENGN_OPENGL;
538#endif
539 // Warning against invalid ports
540 for (const auto &port :
541 {
542 &Config.Network.PortTCP,
543 &Config.Network.PortUDP,
544 &Config.Network.PortDiscovery,
545 &Config.Network.PortRefServer
546 }
547 )
548 {
549 if (*port < 0 || *port > 65535) *port = 0;
550 }
551 if (Config.Network.PortTCP > 0 && Config.Network.PortTCP == Config.Network.PortRefServer)
552 {
553 spdlog::warn(msg: "Network TCP port and reference server port both set to same value - increasing reference server port!");
554 ++Config.Network.PortRefServer;
555 if (Config.Network.PortRefServer >= 65536) Config.Network.PortRefServer = C4NetStdPortRefServer;
556 }
557 if (Config.Network.PortUDP > 0 && Config.Network.PortUDP == Config.Network.PortDiscovery)
558 {
559 spdlog::warn(msg: "Network UDP port and LAN game discovery port both set to same value - increasing discovery port!");
560 ++Config.Network.PortDiscovery;
561 if (Config.Network.PortDiscovery >= 65536) Config.Network.PortDiscovery = C4NetStdPortDiscovery;
562 }
563#endif
564 fConfigLoaded = true;
565 if (szConfigFile) ConfigFilename.Copy(pnData: szConfigFile); else ConfigFilename.Clear();
566 return true;
567}
568
569bool C4Config::Save()
570{
571 try
572 {
573#ifdef _WIN32
574 if (!ConfigFilename.getLength())
575 {
576 // Windows: Default save to registry, if it wasn't loaded from file
577 StdCompilerConfigWrite CfgWrite(HKEY_CURRENT_USER, "Software\\" C4CFG_Company "\\" C4CFG_Product);
578 CfgWrite.Decompile(*this);
579 }
580 else
581#endif
582 {
583 StdStrBuf filename;
584 if (ConfigFilename.getLength())
585 {
586 filename.Ref(Buf2: ConfigFilename);
587 }
588 else
589 {
590 filename.Copy(pnData: getenv(name: "HOME"));
591 if (filename) { filename += "/"; }
592#ifdef __APPLE__
593 filename += "Library/Preferences/legacyclonk.config";
594#else
595 filename += ".legacyclonk/config";
596#endif
597 }
598 StdCompilerINIWrite IniWrite;
599 IniWrite.Decompile(rStruct: *this);
600 const std::string output{IniWrite.getOutput()};
601 StdStrBuf{output.c_str(), output.size(), false}.SaveToFile(szFile: filename.getData());
602 }
603 }
604 catch ([[maybe_unused]] const StdCompiler::Exception &e)
605 {
606#ifdef C4ENGINE
607 Log(id: C4ResStrTableKey::IDS_ERR_CONFSAVE, args: e.what());
608#endif
609 return false;
610 }
611 return true;
612}
613
614void C4ConfigGeneral::DeterminePaths(bool forceWorkingDirectory)
615{
616#ifdef _WIN32
617 // Exe path
618 if (GetModuleFileNameA(nullptr, ExePath, CFG_MaxString))
619 {
620 TruncatePath(ExePath); AppendBackslash(ExePath);
621 }
622 // Temp path
623 GetTempPathA(CFG_MaxString, TempPath);
624 if (TempPath[0]) AppendBackslash(TempPath);
625#elif defined(__linux__)
626#ifdef C4ENGINE
627 GetParentPath(szFilename: Application.Location, szBuffer: ExePath);
628#else
629 ExePath[0] = '.'; ExePath[1] = 0;
630#endif
631 AppendBackslash(szFileName: ExePath);
632 const char *t = getenv(name: "TMPDIR");
633 if (t)
634 {
635 SCopy(szSource: t, sTarget: TempPath, iMaxL: sizeof(TempPath) - 2);
636 AppendBackslash(szFileName: TempPath);
637 }
638 else
639 SCopy(szSource: "/tmp/", sTarget: TempPath);
640#else
641 // Mac: Just use the working directory as ExePath.
642 SCopy(GetWorkingDirectory(), ExePath);
643 AppendBackslash(ExePath);
644 SCopy("/tmp/", TempPath);
645#endif
646 // Force working directory to exe path if desired
647 if (forceWorkingDirectory)
648 SetWorkingDirectory(ExePath);
649 // Log path
650 SCopy(szSource: ExePath, sTarget: LogPath);
651 if (LogPath[0]) AppendBackslash(szFileName: LogPath);
652 else SCopy(szSource: ExePath, sTarget: LogPath);
653 // Screenshot path
654 SCopy(szSource: ExePath, sTarget: ScreenshotPath, iMaxL: CFG_MaxString - 1);
655 if (ScreenshotFolder.getLength() + SLen(sptr: ScreenshotPath) + 1 <= CFG_MaxString)
656 {
657 SAppend(szSource: ScreenshotFolder.getData(), szTarget: ScreenshotPath);
658 AppendBackslash(szFileName: ScreenshotPath);
659 }
660 // Player path
661 if (PlayerPath[0]) AppendBackslash(szFileName: PlayerPath);
662#ifdef C4ENGINE
663 // Create user path if it doesn't already exist
664 if (!DirectoryExists(szFileName: Config.AtUserPath(szFilename: "")))
665 MakeDirectory(pathname: Config.AtUserPath(szFilename: ""), nullptr); // currently no error handling here; also: no recursive directory creation
666#endif
667}
668
669char AtPathFilename[_MAX_PATH + 1];
670
671const char *C4Config::AtExePath(const char *szFilename)
672{
673 SCopy(szSource: General.ExePath, sTarget: AtPathFilename, _MAX_PATH);
674 SAppend(szSource: szFilename, szTarget: AtPathFilename, _MAX_PATH);
675 return AtPathFilename;
676}
677
678const char *C4Config::AtUserPath(const char *szFilename)
679{
680 SCopy(szSource: General.UserPath, sTarget: AtPathFilename, _MAX_PATH);
681 ExpandEnvironmentVariables(strPath: AtPathFilename, _MAX_PATH);
682 AppendBackslash(szFileName: AtPathFilename);
683 SAppend(szSource: szFilename, szTarget: AtPathFilename, _MAX_PATH);
684 return AtPathFilename;
685}
686
687const char *C4Config::AtTempPath(const char *szFilename)
688{
689 SCopy(szSource: General.TempPath, sTarget: AtPathFilename, _MAX_PATH);
690 SAppend(szSource: szFilename, szTarget: AtPathFilename, _MAX_PATH);
691 return AtPathFilename;
692}
693
694#ifdef C4ENGINE
695
696const char *C4Config::AtNetworkPath(const char *szFilename)
697{
698 SCopy(szSource: Network.WorkPath, sTarget: AtPathFilename, _MAX_PATH);
699 SAppend(szSource: szFilename, szTarget: AtPathFilename, _MAX_PATH);
700 return AtPathFilename;
701}
702
703#endif
704
705const char *C4Config::AtScreenshotPath(const char *szFilename)
706{
707 SCopy(szSource: General.ScreenshotPath, sTarget: AtPathFilename, _MAX_PATH);
708 if (const auto len = SLen(sptr: AtPathFilename); len > 0)
709 if (AtPathFilename[len - 1] == DirectorySeparator)
710 AtPathFilename[len - 1] = '\0';
711 if (!DirectoryExists(szFileName: AtPathFilename) && !MakeDirectory(pathname: AtPathFilename, nullptr))
712 {
713 SCopy(szSource: General.ExePath, sTarget: General.ScreenshotPath, iMaxL: CFG_MaxString - 1);
714 SCopy(szSource: General.ScreenshotPath, sTarget: AtPathFilename, _MAX_PATH);
715 }
716 else
717 AppendBackslash(szFileName: AtPathFilename);
718 SAppend(szSource: szFilename, szTarget: AtPathFilename, _MAX_PATH);
719 return AtPathFilename;
720}
721
722#ifdef C4ENGINE
723
724bool C4ConfigGeneral::CreateSaveFolder(const char *strDirectory, const char *strLanguageTitle)
725{
726 // Create directory if needed
727 if (!DirectoryExists(szFileName: strDirectory))
728 if (!MakeDirectory(pathname: strDirectory, nullptr))
729 return false;
730 // Create title component if needed
731 char lang[3]; SCopy(szSource: Config.General.Language, sTarget: lang, iMaxL: 2);
732 const std::string titleFile{std::format(fmt: "{}" DirSep C4CFN_WriteTitle, args&: strDirectory)};
733 const std::string titleData{std::format(fmt: "{}:{}", args: +lang, args&: strLanguageTitle)};
734 CStdFile hFile;
735 if (!FileExists(szFileName: titleFile.c_str()))
736 if (!hFile.Create(szFileName: titleFile.c_str()) || !hFile.WriteString(szStr: titleData.c_str()) || !hFile.Close())
737 return false;
738 // Save folder seems okay
739 return true;
740}
741
742const char *C4ConfigNetwork::GetLeagueServerAddress()
743{
744 // Alternate (GUI configurable) league server
745 if (UseAlternateServer)
746 return AlternateServerAddress;
747 // Standard (registry/config file configurable) official league server
748 else
749 return ServerAddress;
750}
751
752void C4ConfigControls::ResetKeys()
753{
754 StdCompilerNull Comp; Comp.Compile(rStruct: mkParAdapt(rObj&: *this, rPar: true));
755}
756
757#endif
758
759const char *C4Config::AtExeRelativePath(const char *szFilename)
760{
761 // Specified file is located in ExePath: return relative path
762 return GetRelativePathS(strPath: szFilename, strRelativeTo: General.ExePath);
763}
764
765void C4Config::ForceRelativePath(StdStrBuf *sFilename)
766{
767 assert(sFilename);
768 // Specified file is located in ExePath?
769 const char *szRelative = GetRelativePathS(strPath: sFilename->getData(), strRelativeTo: General.ExePath);
770 if (szRelative != sFilename->getData())
771 {
772 // return relative path
773 StdStrBuf sTemp; sTemp.Copy(pnData: szRelative);
774 sFilename->Take(Buf2&: sTemp);
775 }
776 else
777 {
778 // not in ExePath: Is it a global path?
779 if (IsGlobalPath(szPath: sFilename->getData()))
780 {
781 // then shorten it (e.g. C:\Temp\Missions.c4f\Goldmine.c4s to Missions.c4f\Goldmine.c4s)
782 StdStrBuf sTemp; sTemp.Copy(pnData: GetC4Filename(szPath: sFilename->getData()));
783 sFilename->Take(Buf2&: sTemp);
784 }
785 }
786}
787
788void C4ConfigGeneral::DefaultLanguage()
789{
790 // No language defined: default to German or English by system language
791 if (!Language[0])
792 {
793 if (isGermanSystem())
794 SCopy(szSource: "DE - Deutsch", sTarget: Language);
795 else
796 SCopy(szSource: "US - English", sTarget: Language);
797 }
798 // No fallback sequence defined: use primary language list
799 if (!LanguageEx[0])
800 GetLanguageSequence(strSource: Language, strTarget: LanguageEx);
801}
802
803bool C4Config::Init()
804{
805 return true;
806}
807
808const char *C4Config::GetSubkeyPath(const char *strSubkey)
809{
810 static char key[1024 + 1];
811#ifdef _WIN32
812 FormatWithNull(key, "Software\\{}\\{}\\{}", +C4CFG_Company, +C4CFG_Product, strSubkey);
813#else
814 SCopy(szSource: strSubkey, sTarget: key, iMaxL: 1024);
815#endif
816 return key;
817}
818
819int C4ConfigGeneral::GetLanguageSequence(const char *strSource, char *strTarget)
820{
821 // Copy a condensed list of language codes from the source list to the target string,
822 // skipping any whitespace or long language descriptions. Language sequences are
823 // comma separated.
824 int iCount = 0;
825 char strLang[2 + 1];
826 for (int i = 0; SCopySegment(fstr: strSource, segn: i, tstr: strLang, sepa: ',', iMaxL: 2, fSkipWhitespace: true); i++)
827 if (strLang[0])
828 {
829 if (strTarget[0]) SAppendChar(cChar: ',', szStr: strTarget);
830 SAppend(szSource: strLang, szTarget: strTarget);
831 iCount++;
832 }
833 return iCount;
834}
835
836#ifdef C4ENGINE
837
838void C4ConfigStartup::CompileFunc(StdCompiler *pComp)
839{
840 pComp->Value(rStruct: mkNamingAdapt(rValue&: HideMsgStartDedicated, szName: "HideMsgStartDedicated", rDefault: false));
841 pComp->Value(rStruct: mkNamingAdapt(rValue&: HideMsgPlrTakeOver, szName: "HideMsgPlrTakeOver", rDefault: false));
842 pComp->Value(rStruct: mkNamingAdapt(rValue&: HideMsgPlrNoTakeOver, szName: "HideMsgPlrNoTakeOver", rDefault: false));
843 pComp->Value(rStruct: mkNamingAdapt(rValue&: HideMsgNoOfficialLeague, szName: "HideMsgNoOfficialLeague", rDefault: false));
844 pComp->Value(rStruct: mkNamingAdapt(rValue&: HideMsgIRCDangerous, szName: "HideMsgIRCDangerous", rDefault: false));
845 pComp->Value(rStruct: mkNamingAdapt(rValue&: AlphabeticalSorting, szName: "AlphabeticalSorting", rDefault: false));
846 pComp->Value(rStruct: mkNamingAdapt(rValue&: LastPortraitFolderIdx, szName: "LastPortraitFolderIdx", rDefault: 0));
847}
848
849#endif
850
851void C4Config::CompileFunc(StdCompiler *pComp)
852{
853 pComp->Value(rStruct: mkNamingAdapt(rValue&: General, szName: "General"));
854#ifdef C4ENGINE
855 pComp->Value(rStruct: mkNamingAdapt(rValue&: Controls, szName: "Controls"));
856 for (int i = 0; i < C4ConfigMaxGamepads; ++i)
857 pComp->Value(rStruct: mkNamingAdapt(rValue&: Gamepads[i], szName: std::format(fmt: "Gamepad{}", args&: i).c_str()));
858 pComp->Value(rStruct: mkNamingAdapt(rValue&: Graphics, szName: "Graphics"));
859 pComp->Value(rStruct: mkNamingAdapt(rValue&: Sound, szName: "Sound"));
860 pComp->Value(rStruct: mkNamingAdapt(rValue&: Network, szName: "Network"));
861 pComp->Value(rStruct: mkNamingAdapt(rValue&: Lobby, szName: "Lobby"));
862 pComp->Value(rStruct: mkNamingAdapt(rValue&: IRC, szName: "IRC"));
863 pComp->Value(rStruct: mkNamingAdapt(rValue&: Developer, szName: "Developer"));
864 pComp->Value(rStruct: mkNamingAdapt(rValue&: Startup, szName: "Startup"));
865 pComp->Value(rStruct: mkNamingAdapt(rValue&: Cooldowns, szName: "Cooldowns"));
866 pComp->Value(rStruct: mkNamingAdapt(rValue&: Toasts, szName: "Toasts"));
867 pComp->Value(rStruct: mkNamingAdapt(rValue&: Logging, szName: "Logging"));
868#endif
869}
870
871// The internal clonk charset is one of the windows charsets
872// But to save the used one to the configuration, a string is used
873// So we need to convert this string to the windows number for windows
874// and RTF, and to the iconv name for iconv
875const char *C4Config::GetCharsetCodeName(const char *const charset) noexcept
876{
877 // Match charset name to WinGDI codes
878 if (SEqualNoCase(szStr1: charset, szStr2: "SHIFTJIS")) return "CP932";
879 if (SEqualNoCase(szStr1: charset, szStr2: "HANGUL")) return "CP949";
880 if (SEqualNoCase(szStr1: charset, szStr2: "JOHAB")) return "CP1361";
881 if (SEqualNoCase(szStr1: charset, szStr2: "CHINESEBIG5")) return "CP950";
882 if (SEqualNoCase(szStr1: charset, szStr2: "GREEK")) return "CP1253";
883 if (SEqualNoCase(szStr1: charset, szStr2: "TURKISH")) return "CP1254";
884 if (SEqualNoCase(szStr1: charset, szStr2: "VIETNAMESE")) return "CP1258";
885 if (SEqualNoCase(szStr1: charset, szStr2: "HEBREW")) return "CP1255";
886 if (SEqualNoCase(szStr1: charset, szStr2: "ARABIC")) return "CP1256";
887 if (SEqualNoCase(szStr1: charset, szStr2: "BALTIC")) return "CP1257";
888 if (SEqualNoCase(szStr1: charset, szStr2: "RUSSIAN")) return "CP1251";
889 if (SEqualNoCase(szStr1: charset, szStr2: "THAI")) return "CP874";
890 if (SEqualNoCase(szStr1: charset, szStr2: "EASTEUROPE")) return "CP1250";
891 if (SEqualNoCase(szStr1: charset, szStr2: "UTF-8")) return "UTF-8";
892 // Default
893 return "CP1252";
894}
895
896std::uint8_t C4Config::GetCharsetCode(const char *const charset) noexcept
897{
898 // Match charset name to WinGDI codes
899 if (SEqualNoCase(szStr1: charset, szStr2: "SHIFTJIS")) return 128; // SHIFTJIS_CHARSET
900 if (SEqualNoCase(szStr1: charset, szStr2: "HANGUL")) return 129; // HANGUL_CHARSET
901 if (SEqualNoCase(szStr1: charset, szStr2: "JOHAB")) return 130; // JOHAB_CHARSET
902 if (SEqualNoCase(szStr1: charset, szStr2: "CHINESEBIG5")) return 136; // CHINESEBIG5_CHARSET
903 if (SEqualNoCase(szStr1: charset, szStr2: "GREEK")) return 161; // GREEK_CHARSET
904 if (SEqualNoCase(szStr1: charset, szStr2: "TURKISH")) return 162; // TURKISH_CHARSET
905 if (SEqualNoCase(szStr1: charset, szStr2: "VIETNAMESE")) return 163; // VIETNAMESE_CHARSET
906 if (SEqualNoCase(szStr1: charset, szStr2: "HEBREW")) return 177; // HEBREW_CHARSET
907 if (SEqualNoCase(szStr1: charset, szStr2: "ARABIC")) return 178; // ARABIC_CHARSET
908 if (SEqualNoCase(szStr1: charset, szStr2: "BALTIC")) return 186; // BALTIC_CHARSET
909 if (SEqualNoCase(szStr1: charset, szStr2: "RUSSIAN")) return 204; // RUSSIAN_CHARSET
910 if (SEqualNoCase(szStr1: charset, szStr2: "THAI")) return 222; // THAI_CHARSET
911 if (SEqualNoCase(szStr1: charset, szStr2: "EASTEUROPE")) return 238; // EASTEUROPE_CHARSET
912 if (SEqualNoCase(szStr1: charset, szStr2: "UTF-8")) return 0; // ANSI_CHARSET - UTF8 needs special handling
913 // Default
914 return 0; // ANSI_CHARSET
915}
916
917
918std::int32_t C4Config::GetCharsetCodePage(const char *const charset) noexcept
919{
920 // Match charset name to WinGDI codes
921 if (SEqualNoCase(szStr1: charset, szStr2: "SHIFTJIS")) return 932;
922 if (SEqualNoCase(szStr1: charset, szStr2: "HANGUL")) return 949;
923 if (SEqualNoCase(szStr1: charset, szStr2: "JOHAB")) return 1361;
924 if (SEqualNoCase(szStr1: charset, szStr2: "CHINESEBIG5")) return 950;
925 if (SEqualNoCase(szStr1: charset, szStr2: "GREEK")) return 1253;
926 if (SEqualNoCase(szStr1: charset, szStr2: "TURKISH")) return 1254;
927 if (SEqualNoCase(szStr1: charset, szStr2: "VIETNAMESE")) return 1258;
928 if (SEqualNoCase(szStr1: charset, szStr2: "HEBREW")) return 1255;
929 if (SEqualNoCase(szStr1: charset, szStr2: "ARABIC")) return 1256;
930 if (SEqualNoCase(szStr1: charset, szStr2: "BALTIC")) return 1257;
931 if (SEqualNoCase(szStr1: charset, szStr2: "RUSSIAN")) return 1251;
932 if (SEqualNoCase(szStr1: charset, szStr2: "THAI")) return 874;
933 if (SEqualNoCase(szStr1: charset, szStr2: "EASTEUROPE")) return 1250;
934 if (SEqualNoCase(szStr1: charset, szStr2: "UTF-8")) return -1; // shouldn't be called
935 // Default
936 return 1252;
937}
938
939void C4Config::ExpandEnvironmentVariables(char *strPath, int iMaxLen)
940{
941#ifdef _WIN32
942 char buf[_MAX_PATH + 1];
943 ExpandEnvironmentStringsA(strPath, buf, _MAX_PATH);
944 SCopy(buf, strPath, iMaxLen);
945#else // __linux__ or __APPLE___
946 StdStrBuf home(getenv(name: "HOME"), false);
947 char *rest;
948 if (home && (rest = const_cast<char *>(SSearch(szString: strPath, szIndex: "$HOME"))) && (SLen(sptr: strPath) - 5 + home.getLength() <= iMaxLen))
949 {
950 // String replace... there might be a more elegant way to do this.
951 memmove(dest: rest + home.getLength() - SLen(sptr: "$HOME"), src: rest, n: SLen(sptr: rest) + 1);
952 strncpy(dest: rest - SLen(sptr: "$HOME"), src: home.getData(), n: home.getLength());
953 }
954#endif
955}
956
957#ifdef C4ENGINE
958void C4Config::AdaptToCurrentVersion()
959{
960 switch (General.Version)
961 {
962#ifdef __APPLE__
963 case 349:
964 // Mac: set Preloading to false due to it being crash-prone
965 General.Preloading = false;
966 break;
967#endif
968
969 case 347:
970 // reset max channels
971 Sound.MaxChannels = C4AudioSystem::MaxChannels;
972 [[fallthrough]];
973
974 case 346:
975 // reenable ingame music
976 Sound.RXMusic = true;
977 break;
978
979 default:
980 break;
981 }
982
983 if (General.Version <= 359)
984 {
985 constexpr auto migrate = [](char *const field, const char *const oldAddress, const char *const newAddress)
986 {
987 if (SEqual(szStr1: field, szStr2: oldAddress))
988 {
989 std::strncpy(dest: field, src: newAddress, n: CFG_MaxString);
990 }
991 };
992
993 migrate(Network.ServerAddress, "league.clonkspot.org:80", C4CFG_LeagueServer);
994 migrate(Network.AlternateServerAddress, "league.clonkspot.org:80", C4CFG_FallbackServer);
995 migrate(Network.UpdateServerAddress, "update.clonkspot.org/lc/update", C4CFG_UpdateServer);
996 migrate(Network.PuncherAddress, "clonk.de:11115", C4ConfigNetwork::DefaultPuncherServer);
997
998 // enable shaders
999 Graphics.Shader = true;
1000 // reenable gamma
1001 Graphics.DisableGamma = false;
1002 }
1003
1004 General.Version = C4XVERBUILD;
1005}
1006#endif
1007