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