1 /*
2 * OpenClonk, http://www.openclonk.org
3 * Copyright (c) 1998-2000, Matthes Bender
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk 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 "C4Include.h"
20 #include "C4ForbidLibraryCompilation.h"
21 #include "config/C4Config.h"
22
23 #include "C4Version.h"
24 #include "c4group/C4Components.h"
25 #include "network/C4Network2.h"
26
27 #include "platform/C4Window.h"
28 #include "platform/StdRegistry.h"
29
30 #ifdef HAVE_SYS_STAT_H
31 #include <sys/stat.h>
32 #endif
33 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36 #ifdef HAVE_LOCALE_H
37 #include <clocale>
38 #endif
39
40 #ifdef USE_CONSOLE
41 #define DONCOFF 0
42 #else
43 #define DONCOFF 1
44 #endif
45
46 #include "game/C4Application.h"
47
CompileFunc(StdCompiler * pComp)48 void C4ConfigGeneral::CompileFunc(StdCompiler *pComp)
49 {
50 // For those without the ability to intuitively guess what the falses and trues mean:
51 // its mkNamingAdapt(field, name, default, fPrefillDefault, fStoreDefault)
52 // where fStoreDefault writes out the value to the config even if it's the same as the default.
53 #define s mkStringAdaptM
54 pComp->Value(mkNamingAdapt(s(Name), "Name", "" ));
55 pComp->Value(mkNamingAdapt(s(Language), "Language", "", false, true));
56 pComp->Value(mkNamingAdapt(s(LanguageEx), "LanguageEx", "", false, true));
57 pComp->Value(mkNamingAdapt(s(Participants), "Participants", "" ));
58
59 // deliberately not grandfathering UserPath setting, since it was written to config by default
60 pComp->Value(mkNamingAdapt(s(ConfigUserPath), "UserDataPath", "", false, true));
61 // assimilate old data
62 pComp->Value(mkNamingAdapt(s(Adopt.PlayerPath), "PlayerPath", ""));
63
64 // temporary path only set during updates
65 pComp->Value(mkNamingAdapt(s(TempUpdatePath), "TempUpdatePath", ""));
66
67 pComp->Value(mkNamingAdapt(s(MissionAccess), "MissionAccess", "", false, true));
68 pComp->Value(mkNamingAdapt(FPS, "FPS", 0 ));
69 pComp->Value(mkNamingAdapt(DefRec, "DefRec", 0 ));
70 pComp->Value(mkNamingAdapt(ScreenshotFolder, "ScreenshotFolder", "Screenshots", false, true));
71 pComp->Value(mkNamingAdapt(ScrollSmooth, "ScrollSmooth", 4 ));
72 pComp->Value(mkNamingAdapt(AlwaysDebug, "DebugMode", 0 ));
73 pComp->Value(mkNamingAdapt(OpenScenarioInGameMode, "OpenScenarioInGameMode", 0 ));
74 #ifdef _WIN32
75 pComp->Value(mkNamingAdapt(MMTimer, "MMTimer", 1 ));
76 #endif
77 pComp->Value(mkNamingAdapt(s(RXFontName), "FontName", C4DEFAULT_FONT_NAME, false, true));
78 pComp->Value(mkNamingAdapt(RXFontSize, "FontSize", 14, false, true));
79 pComp->Value(mkNamingAdapt(GamepadEnabled, "GamepadEnabled", true ));
80 pComp->Value(mkNamingAdapt(FirstStart, "FirstStart", true ));
81 pComp->Value(mkNamingAdapt(ConfigResetSafety, "ConfigResetSafety", static_cast<int32_t>(ConfigResetSafetyVal) ));
82 }
83
CompileFunc(StdCompiler * pComp)84 void C4ConfigDeveloper::CompileFunc(StdCompiler *pComp)
85 {
86 pComp->Value(mkNamingAdapt(AutoFileReload, "AutoFileReload", 1 , false, true));
87 pComp->Value(mkNamingAdapt(s(TodoFilename), "TodoFilename", "{SCENARIO}/TODO.txt", false, true));
88 pComp->Value(mkNamingAdapt(s(AltTodoFilename), "AltTodoFilename2", "{USERPATH}/TODO.txt", false, true));
89 pComp->Value(mkNamingAdapt(MaxScriptMRU, "MaxScriptMRU", 30 , false, false));
90 pComp->Value(mkNamingAdapt(DebugShapeTextures, "DebugShapeTextures", 0 , false, true));
91 pComp->Value(mkNamingAdapt(ShowHelp, "ShowHelp", true , false, false));
92 for (int32_t i = 0; i < CFG_MaxEditorMRU; ++i)
93 pComp->Value(mkNamingAdapt(s(RecentlyEditedSzenarios[i]), FormatString("EditorMRU%02d", (int)i).getData(), "", false, false));
94 }
95
AddRecentlyEditedScenario(const char * fn)96 void C4ConfigDeveloper::AddRecentlyEditedScenario(const char *fn)
97 {
98 if (!fn || !*fn) return;
99 // Put given scenario first in list by moving all other scenarios down
100 // Check how many scenarios to move down the list. Stop moving down when the given scenario is in the list
101 int32_t move_down_num;
102 for (move_down_num = 0; move_down_num < CFG_MaxEditorMRU - 1; ++move_down_num)
103 if (!strncmp(fn, RecentlyEditedSzenarios[move_down_num], CFG_MaxString))
104 break;
105 // Move them down
106 for (int32_t i = move_down_num; i > 0; --i)
107 strcpy(RecentlyEditedSzenarios[i], RecentlyEditedSzenarios[i - 1]);
108 // Put current scenario in
109 strncpy(RecentlyEditedSzenarios[0], fn, CFG_MaxString);
110 }
111
CompileFunc(StdCompiler * pComp)112 void C4ConfigGraphics::CompileFunc(StdCompiler *pComp)
113 {
114 pComp->Value(mkNamingAdapt(ResX, "ResolutionX", -1 ,false, true));
115 pComp->Value(mkNamingAdapt(ResY, "ResolutionY", -1 ,false, true));
116 pComp->Value(mkNamingAdapt(WindowX, "WindowX", 800 ,false, true));
117 pComp->Value(mkNamingAdapt(WindowY, "WindowY", 600 ,false, true));
118 pComp->Value(mkNamingAdapt(RefreshRate, "RefreshRate", 0 ));
119 pComp->Value(mkNamingAdapt(SplitscreenDividers, "SplitscreenDividers", 1 ));
120 pComp->Value(mkNamingAdapt(ShowStartupMessages, "ShowStartupMessages", 1 ,false, true));
121 pComp->Value(mkNamingAdapt(VerboseObjectLoading, "VerboseObjectLoading", 0 ));
122 pComp->Value(mkNamingAdapt(MenuTransparency, "MenuTransparency", 1 ,false, true));
123 pComp->Value(mkNamingAdapt(UpperBoard, "UpperBoard", 1 ,false, true));
124 pComp->Value(mkNamingAdapt(ShowClock, "ShowClock", 0 ,false, true));
125 pComp->Value(mkNamingAdapt(ShowCrewNames, "ShowCrewNames", 1 ,false, true));
126 pComp->Value(mkNamingAdapt(ShowCrewCNames, "ShowCrewCNames", 0 ,false, true));
127 pComp->Value(mkNamingAdapt(Windowed, "Windowed", 0 ,false, true));
128 pComp->Value(mkNamingAdapt(PXSGfx, "PXSGfx" , 1 ));
129 pComp->Value(mkNamingAdapt(Gamma, "Gamma" , 100 ));
130 pComp->Value(mkNamingAdapt(Currency, "Currency" , 0 ));
131 pComp->Value(mkNamingAdapt(Monitor, "Monitor", 0 )); // 0 = D3DADAPTER_DEFAULT
132 pComp->Value(mkNamingAdapt(MaxRefreshDelay, "MaxRefreshDelay", 30 ));
133 pComp->Value(mkNamingAdapt(NoOffscreenBlits, "NoOffscreenBlits", 1 ));
134 pComp->Value(mkNamingAdapt(MultiSampling, "MultiSampling", 4 ));
135 pComp->Value(mkNamingAdapt(AutoFrameSkip, "AutoFrameSkip", 1 ));
136 pComp->Value(mkNamingAdapt(MouseCursorSize, "MouseCursorSize", 50 ));
137 }
138
CompileFunc(StdCompiler * pComp)139 void C4ConfigSound::CompileFunc(StdCompiler *pComp)
140 {
141 pComp->Value(mkNamingAdapt(RXSound, "Sound", DONCOFF ,false, true));
142 pComp->Value(mkNamingAdapt(RXMusic, "Music", DONCOFF ,false, true));
143 pComp->Value(mkNamingAdapt(FEMusic, "MenuMusic", DONCOFF ,false, true));
144 pComp->Value(mkNamingAdapt(FESamples, "MenuSound", DONCOFF ,false, true));
145 pComp->Value(mkNamingAdapt(Verbose, "Verbose", 0 ));
146 pComp->Value(mkNamingAdapt(MusicVolume, "MusicVolume2", 40 ,false, true));
147 pComp->Value(mkNamingAdapt(SoundVolume, "SoundVolume", 100 ,false, true));
148 }
149
CompileFunc(StdCompiler * pComp)150 void C4ConfigNetwork::CompileFunc(StdCompiler *pComp)
151 {
152 pComp->Value(mkNamingAdapt(ControlRate, "ControlRate", 3 ,false, true));
153 pComp->Value(mkNamingAdapt(ControlPreSend, "ControlPreSend", -1 ));
154 pComp->Value(mkNamingAdapt(s(WorkPath), "WorkPath", "Network" ,false, true));
155 pComp->Value(mkNamingAdapt(Lobby, "Lobby", 0 ));
156 pComp->Value(mkNamingAdapt(NoRuntimeJoin, "NoRuntimeJoin", 1 ,false, true));
157 pComp->Value(mkNamingAdapt(NoReferenceRequest, "NoReferenceRequest", 0 ));
158 pComp->Value(mkNamingAdapt(MaxResSearchRecursion, "MaxResSearchRecursion",1 ,false, true));
159 pComp->Value(mkNamingAdapt(Comment, "Comment", "" ,false, true));
160 pComp->Value(mkNamingAdapt(PortTCP, "PortTCP", C4NetStdPortTCP ,false, true));
161 pComp->Value(mkNamingAdapt(PortUDP, "PortUDP", C4NetStdPortUDP ,false, true));
162 pComp->Value(mkNamingAdapt(EnableUPnP, "EnableUPnP", 1 , false, true));
163 pComp->Value(mkNamingAdapt(PortDiscovery, "PortDiscovery", C4NetStdPortDiscovery ,false, true));
164 pComp->Value(mkNamingAdapt(PortRefServer, "PortRefServer", C4NetStdPortRefServer ,false, true));
165 pComp->Value(mkNamingAdapt(ControlMode, "ControlMode", 0 ));
166 pComp->Value(mkNamingAdapt(Nick, "Nick", "" ,false, true));
167 pComp->Value(mkNamingAdapt(MaxLoadFileSize, "MaxLoadFileSize", 5*1024*1024 ,false, true));
168
169 pComp->Value(mkNamingAdapt(MasterServerSignUp, "MasterServerSignUp", 1 ));
170 pComp->Value(mkNamingAdapt(MasterServerActive, "MasterServerActive", 0 ));
171 pComp->Value(mkNamingAdapt(MasterKeepPeriod, "MasterKeepPeriod", 60 ));
172 pComp->Value(mkNamingAdapt(MasterReferencePeriod, "MasterReferencePeriod",120 ));
173 pComp->Value(mkNamingAdapt(LeagueServerSignUp, "LeagueServerSignUp", 0 ));
174 pComp->Value(mkNamingAdapt(UseAlternateServer, "UseAlternateServer", 0 ));
175 pComp->Value(mkNamingAdapt(s(AlternateServerAddress),"AlternateServerAddress", "league.openclonk.org:80/league.php"));
176 pComp->Value(mkNamingAdapt(s(LastPassword), "LastPassword", "Wipf" ));
177 #ifdef WITH_AUTOMATIC_UPDATE
178 pComp->Value(mkNamingAdapt(s(UpdateServerAddress), "UpdateServerAddress", "www.openclonk.org:80/update/"));
179 pComp->Value(mkNamingAdapt(AutomaticUpdate, "AutomaticUpdate", 0 ,false, true));
180 pComp->Value(mkNamingAdapt(LastUpdateTime, "LastUpdateTime", 0 ));
181 #endif
182 pComp->Value(mkNamingAdapt(AsyncMaxWait, "AsyncMaxWait", 2 ));
183 pComp->Value(mkNamingAdapt(PacketLogging, "PacketLogging", 0 ));
184
185
186 pComp->Value(mkNamingAdapt(s(PuncherAddress), "PuncherAddress", "netpuncher.openclonk.org:11115"));
187 pComp->Value(mkNamingAdapt(mkParAdapt(LastLeagueServer, StdCompiler::RCT_All), "LastLeagueServer", "" ));
188 pComp->Value(mkNamingAdapt(mkParAdapt(LastLeaguePlayerName, StdCompiler::RCT_All), "LastLeaguePlayerName", "" ));
189 pComp->Value(mkNamingAdapt(mkParAdapt(LastLeagueAccount, StdCompiler::RCT_All), "LastLeagueAccount", "" ));
190 pComp->Value(mkNamingAdapt(mkParAdapt(LastLeagueLoginToken, StdCompiler::RCT_All), "LastLeagueLoginToken", "" ));
191 }
192
CompileFunc(StdCompiler * pComp)193 void C4ConfigLobby::CompileFunc(StdCompiler *pComp)
194 {
195 pComp->Value(mkNamingAdapt(AllowPlayerSave, "AllowPlayerSave", 0 ,false, false));
196 pComp->Value(mkNamingAdapt(CountdownTime, "CountdownTime", 5 ,false, false));
197 }
198
CompileFunc(StdCompiler * pComp)199 void C4ConfigIRC::CompileFunc(StdCompiler *pComp)
200 {
201 pComp->Value(mkNamingAdapt(s(Server), "Server", "irc.euirc.net", false, true));
202 pComp->Value(mkNamingAdapt(s(Nick), "Nick", "" , false, true));
203 pComp->Value(mkNamingAdapt(s(RealName), "RealName", "" , false, true));
204 pComp->Value(mkNamingAdapt(s(Channel), "Channel", "#openclonk" , false, true));
205 pComp->Value(mkNamingAdapt(AllowAllChannels, "AllowAllChannels", 0 , false, true));
206 }
207
CompileFunc(StdCompiler * pComp)208 void C4ConfigSecurity::CompileFunc(StdCompiler *pComp)
209 {
210 pComp->Value(mkNamingAdapt(WasRegistered, "WasRegistered", 0 ));
211 #ifdef _WIN32
212 pComp->Value(mkNamingAdapt(s(KeyPath), "KeyPath", R"(%APPDATA%\)" C4ENGINENAME, false, true));
213 #elif defined(__linux__)
214 pComp->Value(mkNamingAdapt(s(KeyPath), "KeyPath", "$HOME/.clonk/" C4ENGINENICK, false, true));
215 #elif defined(__APPLE__)
216 pComp->Value(mkNamingAdapt(s(KeyPath), "KeyPath", "$HOME/Library/Application Support/" C4ENGINENAME, false, true));
217 #endif
218 }
219
CompileFunc(StdCompiler * pComp,bool fButtonsOnly)220 void C4ConfigGamepad::CompileFunc(StdCompiler *pComp, bool fButtonsOnly)
221 {
222 /* The defaults here are for a Logitech Dual Action under Linux-SDL. Better than nothing, I guess. */
223 if (!fButtonsOnly)
224 {
225 for (int i=0; i<6; ++i)
226 {
227 pComp->Value(mkNamingAdapt(AxisMin[i], FormatString("Axis%dMin", i).getData(), 0u));
228 pComp->Value(mkNamingAdapt(AxisMax[i], FormatString("Axis%dMax", i).getData(), 0u));
229 pComp->Value(mkNamingAdapt(AxisCalibrated[i], FormatString("Axis%dCalibrated", i).getData(), false));
230 }
231 }
232 pComp->Value(mkNamingAdapt(Button[0], "Button1", -1 ));
233 pComp->Value(mkNamingAdapt(Button[1], "Button2", -1 ));
234 pComp->Value(mkNamingAdapt(Button[2], "Button3", -1 ));
235 pComp->Value(mkNamingAdapt(Button[3], "Button4", -1 ));
236 pComp->Value(mkNamingAdapt(Button[4], "Button5", -1 ));
237 pComp->Value(mkNamingAdapt(Button[5], "Button6", -1 ));
238 pComp->Value(mkNamingAdapt(Button[6], "Button7", -1 ));
239 pComp->Value(mkNamingAdapt(Button[7], "Button8", -1 ));
240 pComp->Value(mkNamingAdapt(Button[8], "Button9", -1 ));
241 pComp->Value(mkNamingAdapt(Button[9], "Button10", -1 ));
242 pComp->Value(mkNamingAdapt(Button[10], "Button11", -1 ));
243 pComp->Value(mkNamingAdapt(Button[11], "Button12", -1 ));
244 }
245
ResetButtons()246 void C4ConfigGamepad::ResetButtons()
247 {
248 // loads an empty config for the buttons
249 StdCompilerNull Comp; Comp.Compile(mkParAdapt(*this, true));
250 }
251
Reset()252 void C4ConfigGamepad::Reset()
253 {
254 // loads an empty config for the gamepad config
255 StdCompilerNull Comp; Comp.Compile(mkParAdapt(*this, false));
256 }
257
CompileFunc(StdCompiler * pComp)258 void C4ConfigControls::CompileFunc(StdCompiler *pComp)
259 {
260 #ifndef USE_CONSOLE
261 if (pComp->isSerializer())
262 {
263 // The registry compiler is broken with arrays. It doesn't delete extra items if the config got shorter
264 // Solve it by defaulting the array before writing to it.
265 pComp->Default("UserSets");
266 }
267 pComp->Value(mkNamingAdapt(UserSets, "UserSets", C4PlayerControlAssignmentSets()));
268 pComp->Value(mkNamingAdapt(MouseAutoScroll, "MouseAutoScroll", 0 /* change default 33 to enable */ ));
269 pComp->Value(mkNamingAdapt(GamepadGuiControl, "GamepadGuiControl", 0, false, true));
270 #endif
271 }
272
C4Config()273 C4Config::C4Config()
274 {
275 Default();
276 }
277
~C4Config()278 C4Config::~C4Config()
279 {
280 fConfigLoaded = false;
281 }
282
Default()283 void C4Config::Default()
284 {
285 // force default values
286 StdCompilerNull Comp; Comp.Compile(*this);
287 fConfigLoaded=false;
288 }
289
GetConfigFileName(StdStrBuf & filename,const char * szConfigFile)290 void C4Config::GetConfigFileName(StdStrBuf &filename, const char *szConfigFile)
291 {
292 if (szConfigFile)
293 {
294 // Config filename is specified
295 filename.Ref(szConfigFile);
296 }
297 else
298 {
299 // Config filename from home
300 StdStrBuf home(getenv("HOME"));
301 if (home) { home += "/"; }
302 filename.Copy(home);
303 #ifdef __APPLE__
304 filename += "Library/Preferences/" C4ENGINEID ".config";
305 #else
306 filename += ".clonk/" C4ENGINENICK "/config";
307 #endif
308 }
309 }
310
Load(const char * szConfigFile)311 bool C4Config::Load(const char *szConfigFile)
312 {
313 try
314 {
315 #ifdef _WIN32
316 // Windows: Default load from registry, if no explicit config file is specified
317 if (!szConfigFile)
318 {
319 StdCompilerConfigRead CfgRead(HKEY_CURRENT_USER, "Software\\" C4CFG_Company "\\" C4ENGINENAME);
320 CfgRead.Compile(*this);
321 }
322 else
323 #endif
324 {
325 // Nonwindows or explicit config file: Determine filename to load config from
326 StdStrBuf filename;
327 GetConfigFileName(filename, szConfigFile);
328
329 // Load config file into buf
330 StdStrBuf buf;
331 buf.LoadFromFile(filename.getData());
332
333 if (buf.isNull())
334 {
335 // Config file not present?
336 #ifdef __linux__
337 if (!szConfigFile)
338 {
339 StdStrBuf filename(getenv("HOME"));
340 if (filename) { filename += "/"; }
341 filename += ".clonk/" C4ENGINENICK;
342 CreatePath(filename.getData());
343 }
344 #endif
345 // Buggy StdCompiler crashes when compiling a Null-StdStrBuf
346 buf.Ref(" ");
347 }
348
349 // Read config from buffer
350 StdCompilerINIRead IniRead;
351 IniRead.setInput(buf);
352 IniRead.Compile(*this);
353 }
354 }
355 catch (StdCompiler::Exception *pExc)
356 {
357 // Configuration file syntax error?
358 LogF("Error loading configuration: %s"/*LoadResStr("IDS_ERR_CONFREAD") - restbl not yet loaded*/, pExc->Msg.getData());
359 delete pExc;
360 return false;
361 }
362
363 // Config postinit
364 General.DeterminePaths();
365 #ifdef HAVE_WINSOCK
366 // Setup WS manually, so c4group doesn't depend on C4NetIO
367 WSADATA wsadata;
368 bool fWinSock = !WSAStartup(WINSOCK_VERSION, &wsadata);
369 #endif
370 if (SEqual(Network.Nick.getData(), "Unknown"))
371 {
372 char LocalName[25+1]; *LocalName = 0;
373 gethostname(LocalName, 25);
374 if (*LocalName) Network.Nick.Copy(LocalName);
375 }
376 #ifdef HAVE_WINSOCK
377 if (fWinSock) WSACleanup();
378 #endif
379 General.DefaultLanguage();
380 // Warning against invalid ports
381 if (Config.Network.PortTCP>0 && Config.Network.PortTCP == Config.Network.PortRefServer)
382 {
383 Log("Warning: Network TCP port and reference server port both set to same value - increasing reference server port!");
384 ++Config.Network.PortRefServer;
385 if (Config.Network.PortRefServer>=65536) Config.Network.PortRefServer = C4NetStdPortRefServer;
386 }
387 if (Config.Network.PortUDP>0 && Config.Network.PortUDP == Config.Network.PortDiscovery)
388 {
389 Log("Warning: Network UDP port and LAN game discovery port both set to same value - increasing discovery port!");
390 ++Config.Network.PortDiscovery;
391 if (Config.Network.PortDiscovery>=65536) Config.Network.PortDiscovery = C4NetStdPortDiscovery;
392 }
393 // Empty nick already defaults to GetRegistrationData("Nick") or
394 // Network.LocalName at relevant places.
395 fConfigLoaded = true;
396 if (szConfigFile) ConfigFilename.Copy(szConfigFile); else ConfigFilename.Clear();
397 return true;
398 }
399
Save()400 bool C4Config::Save()
401 {
402 try
403 {
404 #ifdef _WIN32
405 if (!ConfigFilename.getLength())
406 {
407 // Windows: Default save to registry, if it wasn't loaded from file
408 StdCompilerConfigWrite CfgWrite(HKEY_CURRENT_USER, "Software\\" C4CFG_Company "\\" C4ENGINENAME);
409 CfgWrite.Decompile(*this);
410 }
411 else
412 #endif
413 {
414 StdStrBuf filename;
415 GetConfigFileName(filename, ConfigFilename.getLength() ? ConfigFilename.getData() : nullptr);
416 StdCompilerINIWrite IniWrite;
417 IniWrite.Decompile(*this);
418 IniWrite.getOutput().SaveToFile(filename.getData());
419 }
420 }
421 catch (StdCompiler::Exception *pExc)
422 {
423 LogF(LoadResStr("IDS_ERR_CONFSAVE"), pExc->Msg.getData());
424 delete pExc;
425 return false;
426 }
427 return true;
428 }
429
DeterminePaths()430 void C4ConfigGeneral::DeterminePaths()
431 {
432 #ifdef _WIN32
433 // Exe path
434 wchar_t apath[CFG_MaxString];
435 if (GetModuleFileNameW(nullptr,apath,CFG_MaxString))
436 {
437 ExePath = StdStrBuf(apath);
438 TruncatePath(ExePath.getMData());
439 ExePath.SetLength(SLen(ExePath.getMData()));
440 ExePath.AppendBackslash();
441 }
442 // Temp path
443 GetTempPathW(CFG_MaxString,apath);
444 TempPath = StdStrBuf(apath);
445 if (TempPath[0]) TempPath.AppendBackslash();
446 #elif defined(PROC_SELF_EXE)
447 ExePath.SetLength(1024);
448 ssize_t l = readlink(PROC_SELF_EXE, ExePath.getMData(), 1024);
449 if (l < -1)
450 {
451 ExePath.Ref(".");
452 }
453 else
454 {
455 ExePath.SetLength(l);
456 GetParentPath(ExePath.getData(), &ExePath);
457 ExePath.AppendBackslash();
458 }
459 const char * t = getenv("TMPDIR");
460 if (t)
461 {
462 TempPath = t;
463 TempPath.AppendBackslash();
464 }
465 else
466 TempPath = "/tmp/";
467 #else
468 // Mac: Just use the working directory as ExePath.
469 ExePath = GetWorkingDirectory();
470 ExePath.AppendBackslash();
471 TempPath = "/tmp/";
472 #endif
473
474 // Find system-wide data path
475 #if defined(_WIN32)
476 // Use ExePath: on windows, everything is installed to one directory
477 SCopy(ExePath.getMData(),SystemDataPath);
478 #elif defined(__APPLE__)
479 SCopy(::Application.GetGameDataPath().c_str(),SystemDataPath);
480 #elif defined(WITH_AUTOMATIC_UPDATE)
481 // WITH_AUTOMATIC_UPDATE builds are our tarball releases and
482 // development snapshots, i.e. where the game data is at the
483 // same location as the executable.
484 SCopy(ExePath.getMData(),SystemDataPath);
485 #elif defined(OC_SYSTEM_DATA_DIR)
486 SCopy(OC_SYSTEM_DATA_DIR, SystemDataPath);
487 #else
488 #error Please define OC_SYSTEM_DATA_DIR!
489 #endif
490 AppendBackslash(SystemDataPath);
491
492 // Find user-specific data path
493 if (ConfigUserPath[0])
494 SCopy(ConfigUserPath, UserDataPath);
495 else
496 #if defined(_WIN32)
497 SCopy(R"(%APPDATA%\)" C4ENGINENAME, UserDataPath);
498 #elif defined(__APPLE__)
499 SCopy("$HOME/Library/Application Support/" C4ENGINENAME, UserDataPath);
500 #else
501 SCopy("$HOME/.clonk/" C4ENGINENICK, UserDataPath);
502 #endif
503 C4Config::ExpandEnvironmentVariables(UserDataPath, CFG_MaxString);
504 AppendBackslash(UserDataPath);
505
506 // Screenshot path
507 SCopy(UserDataPath, ScreenshotPath, CFG_MaxString-1);
508 if (ScreenshotFolder.getLength()+std::strlen(ScreenshotPath)+1<=CFG_MaxString)
509 {
510 SAppend(ScreenshotFolder.getData(), ScreenshotPath);
511 AppendBackslash(ScreenshotPath);
512 }
513 // Create user path if it doesn't already exist
514 CreatePath(UserDataPath);
515 }
516
517 static char AtPathFilename[_MAX_PATH+1];
518
AtExePath(const char * szFilename)519 const char* C4Config::AtExePath(const char *szFilename)
520 {
521 SCopy(General.ExePath.getData(),AtPathFilename,_MAX_PATH);
522 SAppend(szFilename,AtPathFilename,_MAX_PATH);
523 return AtPathFilename;
524 }
525
AtUserDataPath(const char * szFilename)526 const char* C4Config::AtUserDataPath(const char *szFilename)
527 {
528 SCopy(General.UserDataPath, AtPathFilename, _MAX_PATH);
529 SAppend(szFilename, AtPathFilename, _MAX_PATH);
530 return AtPathFilename;
531 }
532
AtSystemDataPath(const char * szFilename)533 const char* C4Config::AtSystemDataPath(const char *szFilename)
534 {
535 SCopy(General.SystemDataPath, AtPathFilename, _MAX_PATH);
536 SAppend(szFilename, AtPathFilename, _MAX_PATH);
537 return AtPathFilename;
538 }
539
AtTempPath(const char * szFilename)540 const char* C4Config::AtTempPath(const char *szFilename)
541 {
542 SCopy(General.TempPath.getData(),AtPathFilename,_MAX_PATH);
543 SAppend(szFilename,AtPathFilename,_MAX_PATH);
544 return AtPathFilename;
545 }
546
AtNetworkPath(const char * szFilename)547 const char* C4Config::AtNetworkPath(const char *szFilename)
548 {
549 SCopy(General.UserDataPath,AtPathFilename,_MAX_PATH);
550 SAppend(Network.WorkPath,AtPathFilename,_MAX_PATH);
551 SAppend(szFilename,AtPathFilename,_MAX_PATH);
552 return AtPathFilename;
553 }
554
AtScreenshotPath(const char * szFilename)555 const char *C4Config::AtScreenshotPath(const char *szFilename)
556 {
557 int len;
558 SCopy(General.ScreenshotPath,AtPathFilename,_MAX_PATH);
559 if ((len = SLen(AtPathFilename)))
560 if (AtPathFilename[len-1] == DirectorySeparator)
561 AtPathFilename[len-1] = '\0';
562 if (!CreatePath(AtPathFilename))
563 {
564 SCopy(General.UserDataPath,AtPathFilename,_MAX_PATH);
565 }
566 AppendBackslash(AtPathFilename);
567 SAppend(szFilename,AtPathFilename,_MAX_PATH);
568 return AtPathFilename;
569 }
570
571
CreateSaveFolder(const char * strDirectory,const char * strLanguageTitle)572 bool C4ConfigGeneral::CreateSaveFolder(const char *strDirectory, const char *strLanguageTitle)
573 {
574 // Create directory if needed
575 if (!CreatePath(strDirectory))
576 return false;
577 // Create title component if needed
578 char lang[3]; SCopy(Config.General.Language, lang, 2);
579 StdStrBuf strTitleFile; strTitleFile.Format("%s%c%s", strDirectory, DirectorySeparator, C4CFN_WriteTitle);
580 StdStrBuf strTitleData; strTitleData.Format("%s:%s", lang, strLanguageTitle);
581 CStdFile hFile;
582 if (!FileExists(strTitleFile.getData()))
583 if (!hFile.Create(strTitleFile.getData()) || !hFile.WriteString(strTitleData.getData()) || !hFile.Close())
584 return false;
585 // Save folder seems okay
586 return true;
587 }
588
589
GetLeagueServerAddress()590 const char* C4ConfigNetwork::GetLeagueServerAddress()
591 {
592 // Alternate (configurable) league server
593 if (UseAlternateServer)
594 return AlternateServerAddress;
595 // Standard (hardcoded) official league server
596 else
597 return "league.openclonk.org:80/league.php";
598 }
599
CheckPortsForCollisions()600 void C4ConfigNetwork::CheckPortsForCollisions()
601 {
602 // check for port collisions
603 if (PortTCP != -1 && PortTCP == PortRefServer)
604 {
605 LogSilentF("Network: TCP Port collision, setting defaults");
606 PortTCP = C4NetStdPortTCP;
607 PortRefServer = C4NetStdPortRefServer;
608 }
609 if (PortUDP != -1 && PortUDP == PortDiscovery)
610 {
611 LogSilentF("Network: UDP Port collision, setting defaults");
612 PortUDP = C4NetStdPortUDP;
613 PortDiscovery = C4NetStdPortDiscovery;
614 }
615 }
616
SetLeagueLoginData(const char * szServer,const char * szPlayerName,const char * szAccount,const char * szLoginToken)617 void C4ConfigNetwork::SetLeagueLoginData(const char *szServer, const char *szPlayerName, const char *szAccount, const char *szLoginToken)
618 {
619 // ideally, there would be a list to store multiple logins
620 // however, we don't really support multiplayer at one computer at the moment anyway
621 LastLeagueServer.Copy(szServer);
622 LastLeaguePlayerName.Copy(szPlayerName);
623 LastLeagueAccount.Copy(szAccount);
624 LastLeagueLoginToken.Copy(szLoginToken);
625 }
626
GetLeagueLoginData(const char * szServer,const char * szPlayerName,StdStrBuf * pAccount,StdStrBuf * pLoginToken) const627 bool C4ConfigNetwork::GetLeagueLoginData(const char *szServer, const char *szPlayerName, StdStrBuf *pAccount, StdStrBuf *pLoginToken) const
628 {
629 // check if last login matches and store if desired
630 if (LastLeagueServer == szServer && LastLeaguePlayerName == szPlayerName)
631 {
632 pAccount->Copy(LastLeagueAccount);
633 pLoginToken->Copy(LastLeagueLoginToken);
634 return true;
635 }
636 return false;
637 }
638
ResetKeys()639 void C4ConfigControls::ResetKeys()
640 {
641 UserSets.Clear();
642 }
643
AtUserDataRelativePath(const char * szFilename)644 const char* C4Config::AtUserDataRelativePath(const char *szFilename)
645 {
646 // Specified file is located in UserDataPath: return relative path
647 return GetRelativePathS(szFilename, General.UserDataPath);
648 }
649
AtSystemDataRelativePath(const char * szFilename)650 const char* C4Config::AtSystemDataRelativePath(const char *szFilename)
651 {
652 // Specified file is located in SystemDataPath: return relative path
653 return GetRelativePathS(szFilename, General.SystemDataPath);
654 }
655
AtRelativePath(const char * szFilename)656 const char* C4Config::AtRelativePath(const char *szFilename)
657 {
658 const char *szPath = GetRelativePathS(szFilename, General.UserDataPath);
659 if (szPath == szFilename)
660 return GetRelativePathS(szFilename, General.SystemDataPath);
661 return szPath;
662 }
663
ForceRelativePath(StdStrBuf * sFilename)664 void C4Config::ForceRelativePath(StdStrBuf *sFilename)
665 {
666 assert(sFilename);
667 // Specified file is located in SystemDataPath?
668 const char *szRelative = GetRelativePathS(sFilename->getData(), General.SystemDataPath);
669 if (szRelative != sFilename->getData())
670 {
671 // return relative path
672 StdStrBuf sTemp; sTemp.Copy(szRelative);
673 sFilename->Take(std::move(sTemp));
674 }
675 else
676 {
677 // not in ExePath: Is it a global path?
678 if (IsGlobalPath(sFilename->getData()))
679 {
680 // then shorten it (e.g. C:\Temp\Missions.ocf\Goldmine.ocs to Missions.ocf\Goldmine.ocs)
681 StdStrBuf sTemp; sTemp.Copy(GetC4Filename(sFilename->getData()));
682 sFilename->Take(std::move(sTemp));
683 }
684 }
685 }
686
DefaultLanguage()687 void C4ConfigGeneral::DefaultLanguage()
688 {
689 // No language defined: default to German or English by system language
690 if (!Language[0])
691 {
692 if (IsGermanSystem())
693 SCopy("DE - Deutsch", Language);
694 else
695 SCopy("US - English", Language);
696 }
697 // No fallback sequence defined: use primary language list
698 if (!LanguageEx[0])
699 GetLanguageSequence(Language, LanguageEx);
700 }
701
Registered()702 bool C4Config::Registered()
703 {
704 // Dummy function: to be overloaded in C4Config
705 return true;
706 }
707
Init()708 bool C4Config::Init()
709 {
710 return true;
711 }
712
GetSubkeyPath(const char * strSubkey)713 const char* C4Config::GetSubkeyPath(const char *strSubkey)
714 {
715 static char key[1024 + 1];
716 #ifdef _WIN32
717 sprintf(key, R"(Software\%s\%s\%s)", C4CFG_Company, C4ENGINENAME, strSubkey);
718 #else
719 sprintf(key, "%s", strSubkey);
720 #endif
721 return key;
722 }
723
GetLanguageSequence(const char * strSource,char * strTarget)724 int C4ConfigGeneral::GetLanguageSequence(const char *strSource, char *strTarget)
725 {
726 // Copy a condensed list of language codes from the source list to the target string,
727 // skipping any whitespace or long language descriptions. Language sequences are
728 // comma separated.
729 int iCount = 0;
730 char strLang[2 + 1];
731 for (int i = 0; SCopySegment(strSource, i, strLang, ',', 2, true); i++)
732 if (strLang[0])
733 {
734 if (strTarget[0]) SAppendChar(',', strTarget);
735 SAppend(strLang, strTarget);
736 iCount++;
737 }
738 return iCount;
739 }
740
CompileFunc(StdCompiler * pComp)741 void C4ConfigStartup::CompileFunc(StdCompiler *pComp)
742 {
743 pComp->Value(mkNamingAdapt(HideMsgGfxEngineChange, "HideMsgGfxEngineChange", 0));
744 pComp->Value(mkNamingAdapt(HideMsgGfxBitDepthChange, "HideMsgGfxBitDepthChange", 0));
745 pComp->Value(mkNamingAdapt(HideMsgMMTimerChange, "HideMsgMMTimerChange", 0));
746 pComp->Value(mkNamingAdapt(HideMsgStartDedicated, "HideMsgStartDedicated", 0));
747 pComp->Value(mkNamingAdapt(HideMsgPlrTakeOver, "HideMsgPlrTakeOver", 0));
748 pComp->Value(mkNamingAdapt(HideMsgPlrNoTakeOver, "HideMsgPlrNoTakeOver", 0));
749 pComp->Value(mkNamingAdapt(HideMsgNoOfficialLeague, "HideMsgNoOfficialLeague", 0));
750 pComp->Value(mkNamingAdapt(HideMsgIRCDangerous, "HideMsgIRCDangerous", 0));
751 pComp->Value(mkNamingAdapt(AlphabeticalSorting, "AlphabeticalSorting", 0));
752 pComp->Value(mkNamingAdapt(LastPortraitFolderIdx, "LastPortraitFolderIdx", 0));
753 }
754
CompileFunc(StdCompiler * pComp)755 void C4Config::CompileFunc(StdCompiler *pComp)
756 {
757 pComp->Value(mkNamingAdapt(General, "General" ));
758 pComp->Value(mkNamingAdapt(Controls, "Controls" ));
759 for (int i=0; i<C4ConfigMaxGamepads; ++i)
760 pComp->Value(mkNamingAdapt(Gamepads[i], FormatString("Gamepad%d", i).getData()));
761 pComp->Value(mkNamingAdapt(Graphics, "Graphics" ));
762 pComp->Value(mkNamingAdapt(Sound, "Sound" ));
763 pComp->Value(mkNamingAdapt(Network, "Network" ));
764 pComp->Value(mkNamingAdapt(Lobby, "Lobby" ));
765 pComp->Value(mkNamingAdapt(IRC, "IRC" ));
766 pComp->Value(mkNamingAdapt(Developer, "Developer" ));
767 pComp->Value(mkNamingAdapt(Startup, "Startup" ));
768 pComp->Value(mkNamingAdapt(Security, "Security" ));
769 }
770
AddModule(const char * szPath,char * szModules)771 bool C4Config::AddModule(const char *szPath, char *szModules)
772 {
773 return SAddModule(szModules,szPath);
774 }
775
IsModule(const char * szPath,char * szModules)776 bool C4Config::IsModule(const char *szPath, char *szModules)
777 {
778 return SIsModule(szModules,szPath);
779 }
780
RemoveModule(const char * szPath,char * szModules)781 bool C4Config::RemoveModule(const char *szPath, char *szModules)
782 {
783 return SRemoveModule(szModules,szPath);
784 }
785
ExpandEnvironmentVariables(char * strPath,size_t iMaxLen)786 void C4Config::ExpandEnvironmentVariables(char *strPath, size_t iMaxLen)
787 {
788 #ifdef _WIN32
789 wchar_t buf[_MAX_PATH + 1];
790 ExpandEnvironmentStringsW(GetWideChar(strPath), buf, _MAX_PATH);
791 SCopy(StdStrBuf(buf).getData(), strPath, iMaxLen);
792 #else // __linux__ or __APPLE___
793 StdStrBuf home(getenv("HOME"));
794 char* rest;
795 if (home && (rest = const_cast<char *>(SSearch(strPath, "$HOME"))) && (std::strlen(strPath) - 5 + home.getLength() <= iMaxLen))
796 {
797 // String replace... there might be a more elegant way to do this.
798 memmove(rest + home.getLength() - SLen("$HOME"), rest, SLen(rest) + 1);
799 strncpy(rest - SLen("$HOME"), home.getData(), home.getLength());
800 }
801 #endif
802 }
803
CleanupTempUpdateFolder()804 void C4Config::CleanupTempUpdateFolder()
805 {
806 // Get rid of update path present from before update
807 if (*General.TempUpdatePath)
808 {
809 EraseItem(General.TempUpdatePath);
810 *General.TempUpdatePath = '\0';
811 }
812 }
813
MakeTempUpdateFolder()814 const char *C4Config::MakeTempUpdateFolder()
815 {
816 // just pick a temp name
817 StdStrBuf sTempName;
818 sTempName.Copy(AtTempPath("update"));
819 MakeTempFilename(&sTempName);
820 SCopy(sTempName.getData(), General.TempUpdatePath);
821 CreatePath(General.TempUpdatePath);
822 return General.TempUpdatePath;
823 }
824
AtTempUpdatePath(const char * szFilename)825 const char *C4Config::AtTempUpdatePath(const char *szFilename)
826 {
827 SCopy(General.TempUpdatePath,AtPathFilename,_MAX_PATH-1);
828 AppendBackslash(AtPathFilename);
829 SAppend(szFilename,AtPathFilename,_MAX_PATH);
830 return AtPathFilename;
831 }
832
833 C4Config Config;
834