Merge lp:~widelands-dev/widelands/net-uuid into lp:widelands
- net-uuid
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 8503 |
Proposed branch: | lp:~widelands-dev/widelands/net-uuid |
Merge into: | lp:widelands |
Diff against target: |
844 lines (+265/-165) 13 files modified
src/CMakeLists.txt (+1/-0) src/network/CMakeLists.txt (+3/-0) src/network/crypto.cc (+25/-0) src/network/crypto.h (+36/-0) src/network/internet_gaming.cc (+77/-80) src/network/internet_gaming.h (+23/-2) src/network/internet_gaming_protocol.h (+53/-80) src/random/random.cc (+10/-0) src/random/random.h (+6/-0) src/ui_fsmenu/CMakeLists.txt (+1/-0) src/ui_fsmenu/internet_lobby.cc (+5/-2) src/ui_fsmenu/multiplayer.cc (+5/-1) src/wlapplication.cc (+20/-0) |
To merge this branch: | bzr merge lp:~widelands-dev/widelands/net-uuid |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
SirVer | Approve | ||
GunChleoc | Approve | ||
Review via email: mp+332264@code.launchpad.net |
Commit message
Improved the relogin code by adding semi-constant - 12 hour valid - user ids.
Also improved doubled logins with registered accounts.
Description of the change
Improved the relogin code by adding semi-constant user ids.
Reconnecting to the same name on the metaserver should now work for most cases, even when the complete client computer crashes. The id is used to recognize the user and assign his old name to him instead of giving him the requested name. When the user does a clean logout the reservation is removed.
If logging in with a registered account a second time, a new name will be assigned to the new client automatically instead of displaying an error message.
This branch should be merged at the same time as deploying the related branch[1] of the metaserver, since compatibility with the current metaserver version breaks.
[1] https:/
bunnybot (widelandsofficial) wrote : | # |
GunChleoc (gunchleoc) wrote : | # |
Looks mostly OK - I have added some nits.
We should test to make sure that 2 Widelands instances can be run from the same computer and join the same game. I need to figure out how to run a metaserver.
Notabilis (notabilis27) wrote : | # |
Thanks for the review! And for the grammar errors... ehem.
After some testing I am no longer so sure about the trimming of chat messages. Currently it is possible to send space-only messages to everyone or as a whisper message to a single player. So we should probably remove trailing (and leading?) space from all messages. Shall I do so? (A slight disadvantage would be that I can no longer explain the @whisper syntax by prepending it with a space.)
The first NOCOM is related to the fact that the SendMessage m is written to but the SendMessage s is send. It looks pretty broken to me, but has no-one ever tried to send an announcement and discovered this bug? I would just like to have a short confirmation that this is a bug (looking at the code is good enough for me).
As far as I understand it (language-wise), sent_uuid is correct. The variable is a boolean specifying whether the uuid should be sent, not the uuid itself. (The checkbox in the login dialog.)
I can't reproduce the bug of the second NOCOM, probably it was something unrelated to the code. Maybe on layer 8.
All on my local computer: Starting a metaserver and three clients. All Clients try to login with the name "Notabilis", second and third clients receive the names "Notabilis1" and "Notabilis2" respectively. First client opens a game, other two join it. As far as I can tell, everything worked fine.
If this answers your test, fine. But feel free to do some tests on your own. If you want me to setup a metaserver for it, just say so. (Hint: There is a --metaserver=IP command line switch in the game. Took me much too long to notice it.)
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2747. State: failed. Details: https:/
Appveyor build 2559. State: failed. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I opened a new issue over at the metaserver, because I don't know how to run one. So, I'll just rely on your testing here.
As to the 2 NOCOMs, best open new bugs for them and note that they need investigating whether they are bugs.
As to the trimming, you could use it for an .empty() check only to prevent sending empty messages. The font renderer does some trimming on its own, so it shouldn't be an issue when the messages are displayed. For explaining the @whisper command, you could also rephrase it to have the explanation start with "Use @whisper at the beginning of the line to... "
Feel free to merge and deploy this once your'e ready.
Notabilis (notabilis27) wrote : | # |
The NOCOM-bug is really a bug and has been fixed. I fear I introduced it myself a few month ago.
Trimming is also added now. Space is removed in front and at the end of messages. When this results in an empty message, it is dropped. This is also done with the message-part of the @whisper, /motd and /announcement commands.
As far as I am concerned, this can be merged as soon as the metaserver is ready.
GunChleoc (gunchleoc) wrote : | # |
Changes LGTM :)
SirVer (sirver) wrote : | # |
Reviewing now. Sorry that this waited so long for me.
1) third_party/crypto is under "public domain" which is not a license in most parts of the world. I expect issues with debian about this. Can we ask the author to relicense under Apache 2 or MIT? Thes licenses are mostly what the author intended with public domain, but more formal. We can merge this before that happens of course.
2) I am not a fan of 'send_uuid' as a variable name - its semantics are unclear. I do not understand if it means: 'client expects server to send a uuid' or 'server expects client to send a uuid'. Maybe that becomes clearer while I read on, but a better name would be appreciated.
3) internet_
I added some consts, and a few clarifying comments. After addressing these changes this lgtm.
Will review the go code next.
Notabilis (notabilis27) wrote : | # |
Thanks for the review!
1) I replaced the third party crypto code with a call to a boost function. This means that instead of SHA-3 it uses SHA-1 now. This is less secure (not that it matters in this case) but SHA-1 will most likely be needed in a future branch of mine anyway. A slight problem with the boost function: It is part of a ::detail:: namespace so it might become removed at some time.
2) I renamed send_uuid to use_permanent_uuid. Not sure if this is better. What is an improvement is that I removed the variable from the InternetGaming class. It does not need to now whether it is a temporary or permanent ID and calling classes can handle this themselves.
3) Done.
Another point: I replaced my primitive "random string" code with boost::uuid. And then I realized that a simple random number would be just as good. So we can remove the random_string() function if we want to.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2836. State: failed. Details: https:/
Appveyor build 2646. State: success. Details: https:/
SirVer (sirver) wrote : | # |
1) SHA1 is sufficient for our crypto needs for sure. And using a boost function is preferable for now too. We deal with it going away once it happens. Thanks!
2) I was thinking about this a bit and I suggest removing the option and always use the "permanent", i.e. 24h valid UUID. We could reduce the 24 hours to 2h or 5h or so to make sure the UUID shuffles daily and does not become personally identifiable information.
Its only use is to make reconnecting possible with the same nickname, for this we do not need the uuid to stay alive long - it must basically survive the crash and restart of Widelands.
The option is very confusing for users - they need to know what a UUID is and understand how it is used inside of the metaserver. That is an impossible ask. Also, relogin into the same ID is expected behavior for users, so even those that would opt out would still not be happy that they get a new ID assigned on relogin.
So: Get rid of the option and make the behavior default?
3) Yes please! I like uuid() much better and it conveys the semantic better than random_string(). I also like that we use a true uuid in the places where we call our variables uuid. Much better!
Notabilis (notabilis27) wrote : | # |
2) Would be fine by me. The "temporary"
3) I don't really understand what you mean here. I am now using boost::uuid() which seems to be fine. I am not sure whether something else should be changed. Do you want me to drop the string/uuid-string and use a simple integer? Or rename random_string() to generate_uuid()? Or... ?
SirVer (sirver) wrote : | # |
2) 12h sounds good to me. We could go more fancy and rewrite the last_started_
3) Sorry, that was unclear from me. I suggest renaming the function. I did this real quick while thinking how it should be named and investigating if the boost generator could be static in the function. I missed that during review - c++ global variables should be avoided like the plague. https:/
Notabilis (notabilis27) wrote : | # |
The option to use a temporary UUID has been removed and the interval for the "permanent" ones reduced to 12 hours. So I guess this branch is ready for merge now?
Thanks for the link, will be an interesting read. Is that the style guide Widelands (or you?) are trying to follow?
SirVer (sirver) wrote : | # |
> The option to use a temporary UUID has been removed and the interval for the "permanent" ones reduced to 12 hours. So I guess this branch is ready for merge now?
Will have another quick read through in a minute, but yes, I think so. I will merge this and also merge the metaserver branch as soon as this is in. Thanks for working on this! Now on to the even more important relay stuff :).
> Thanks for the link, will be an interesting read. Is that the style guide Widelands (or you?) are trying to follow?
Unfortunately, Widelands predates the Google style guide. Therefore it is not really enforcable in our code base anymore. Also, the style guide mixes style (indent, whitespace, curly braces positions) with good practices (no globals, no non-const references as arguments). Generally it is the best guideline for writing good c++ that I know and I really like the thorough explanations that come with each point.
Having worked at Google for 5 years, I also came to appreciate how many bugs following the style guide avoids. So I usually adhere to it whenever I write new code.
So I'd say, having a read through will give you a lot of food for thought.
SirVer (sirver) wrote : | # |
Looks great! Thanks again.
@bunnybot merge
SirVer (sirver) wrote : | # |
r8472 has messed up the export to GitHub and the git repository state in the related git branch. Travis will not run again for this branch and merging this into trunk will likely mess up the trunk states in git as well. I will do some surgery on this branch and only retain the diff, throwing away all revisions.
bunnybot (widelandsofficial) wrote : | # |
Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.
Travis build 2841. State: failed. Details: https:/
SirVer (sirver) wrote : | # |
For the record, this was the surgery I did:
In the net-uuid branch:
$ bzr diff --diff-options="-u" -r ancestor:lp:widelands > /tmp/diff.patch
Then get a fresh trunk branched:
$ bzr branch lp:widelands
$ patch -p0 < /tmp/diff.patch
$ bzr commit -m "All changes in one clean diff on master." --author "Notabilis <email address hidden>"
$ bzr push --overwrite lp:~widelands-dev/widelands/net-uuid
Unfortunately that made bunnybot unhappy and I had to manually intervene and fix some state. Next time, we should just close the merge request and reopen a new one with the clean patch.
@Notabilis: sorry for loosing your history, but at least the work is still credited to you in full.
@bunnybot merge
Notabilis (notabilis27) wrote : | # |
No problem. As long as we have one commit for the merge the single steps aren't that important.
Seems like I am quite talented with breaking bunnybot though, sorry. So the problem was merging with the same branch? I would have thought that this is a quite normal operation for bzr and git, isn't it? What would have been the bunnybot-friendly way to merge(?) the diverged branch heads?
SirVer (sirver) wrote : | # |
> Seems like I am quite talented with breaking bunnybot though, sorry.
Hardly your fault :/. The whole setup with launchpad -> bzr -> git -> github is kinda fragile by design.
> So the problem was merging with the same branch?
Yes, that was fine for bzr. But in the git import it deleted all the files except for the ones changed in this branch.
> What would have been the bunnybot-friendly way to merge(?) the diverged branch heads?
I think a rebase would have been better. Unfortunately rebase is not part of the core bzr tools, but requires a bzr plugin to be installed: https:/
SirVer (sirver) wrote : | # |
@bunnybot merge
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2851. State: passed. Details: https:/
Appveyor build 2661. State: success. Details: https:/
Notabilis (notabilis27) wrote : | # |
This branch has been merged and the metaserver has been updated, too. This means that previous versions of trunk can no longer connect to the metaserver. Branches based on older trunk are also affected, build 19 should not be affected.
Preview Diff
1 | === modified file 'src/CMakeLists.txt' |
2 | --- src/CMakeLists.txt 2017-11-05 20:06:36 +0000 |
3 | +++ src/CMakeLists.txt 2017-11-26 10:34:25 +0000 |
4 | @@ -121,6 +121,7 @@ |
5 | map_io_map_loader |
6 | network |
7 | profile |
8 | + random |
9 | sound |
10 | ui_basic |
11 | ui_fsmenu |
12 | |
13 | === modified file 'src/network/CMakeLists.txt' |
14 | --- src/network/CMakeLists.txt 2017-11-05 20:06:36 +0000 |
15 | +++ src/network/CMakeLists.txt 2017-11-26 10:34:25 +0000 |
16 | @@ -1,6 +1,8 @@ |
17 | wl_library(network |
18 | SRCS |
19 | constants.h |
20 | + crypto.h |
21 | + crypto.cc |
22 | internet_gaming.cc |
23 | internet_gaming.h |
24 | internet_gaming_messages.cc |
25 | @@ -46,6 +48,7 @@ |
26 | logic_game_settings |
27 | map_io_map_loader |
28 | profile |
29 | + random |
30 | scripting_lua_interface |
31 | scripting_lua_table |
32 | ui_basic |
33 | |
34 | === added file 'src/network/crypto.cc' |
35 | --- src/network/crypto.cc 1970-01-01 00:00:00 +0000 |
36 | +++ src/network/crypto.cc 2017-11-26 10:34:25 +0000 |
37 | @@ -0,0 +1,25 @@ |
38 | +#include "network/crypto.h" |
39 | + |
40 | +#include <boost/uuid/sha1.hpp> |
41 | + |
42 | +namespace crypto { |
43 | + |
44 | +std::string sha1(const std::string& input) { |
45 | + |
46 | + // Hash the input |
47 | + boost::uuids::detail::sha1 sha; |
48 | + sha.process_bytes(input.data(), input.size()); |
49 | + uint32_t digest[5]; |
50 | + sha.get_digest(digest); |
51 | + |
52 | + // Back to string |
53 | + char result[41] = {0}; |
54 | + |
55 | + for (int i = 0; i < 5; i++) { |
56 | + std::sprintf(result + (i << 3), "%08x", digest[i]); |
57 | + } |
58 | + |
59 | + return std::string(result); |
60 | +} |
61 | + |
62 | +} |
63 | |
64 | === added file 'src/network/crypto.h' |
65 | --- src/network/crypto.h 1970-01-01 00:00:00 +0000 |
66 | +++ src/network/crypto.h 2017-11-26 10:34:25 +0000 |
67 | @@ -0,0 +1,36 @@ |
68 | +/* |
69 | + * Copyright (C) 2006-2017 by the Widelands Development Team |
70 | + * |
71 | + * This program is free software; you can redistribute it and/or |
72 | + * modify it under the terms of the GNU General Public License |
73 | + * as published by the Free Software Foundation; either version 2 |
74 | + * of the License, or (at your option) any later version. |
75 | + * |
76 | + * This program is distributed in the hope that it will be useful, |
77 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
78 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
79 | + * GNU General Public License for more details. |
80 | + * |
81 | + * You should have received a copy of the GNU General Public License |
82 | + * along with this program; if not, write to the Free Software |
83 | + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
84 | + * |
85 | + */ |
86 | + |
87 | +#ifndef WL_NETWORK_CRYPTO_H |
88 | +#define WL_NETWORK_CRYPTO_H |
89 | + |
90 | +#include <string> |
91 | + |
92 | +namespace crypto { |
93 | + |
94 | +/** |
95 | + Hashes the given input string with SHA-1 and returns the hash. |
96 | + @param input A string to calculate the hash of. |
97 | + @return The hash as hex-string. |
98 | + */ |
99 | +std::string sha1(const std::string& input); |
100 | + |
101 | +} |
102 | + |
103 | +#endif // end of include guard: WL_NETWORK_CRYPTO_H |
104 | |
105 | === modified file 'src/network/internet_gaming.cc' |
106 | --- src/network/internet_gaming.cc 2017-11-11 14:27:22 +0000 |
107 | +++ src/network/internet_gaming.cc 2017-11-26 10:34:25 +0000 |
108 | @@ -20,8 +20,8 @@ |
109 | #include "network/internet_gaming.h" |
110 | |
111 | #include <memory> |
112 | -#include <random> |
113 | |
114 | +#include <boost/algorithm/string.hpp> |
115 | #include <boost/format.hpp> |
116 | #include <boost/lexical_cast.hpp> |
117 | |
118 | @@ -32,7 +32,9 @@ |
119 | #include "io/fileread.h" |
120 | #include "io/filesystem/layered_filesystem.h" |
121 | #include "network/constants.h" |
122 | +#include "network/crypto.h" |
123 | #include "network/internet_gaming_messages.h" |
124 | +#include "random/random.h" |
125 | |
126 | /// Private constructor by purpose: NEVER call directly. Always call InternetGaming::ref(), this |
127 | /// will ensure |
128 | @@ -63,7 +65,7 @@ |
129 | void InternetGaming::reset() { |
130 | net.reset(); |
131 | state_ = OFFLINE; |
132 | - pwd_ = ""; |
133 | + authenticator_ = ""; |
134 | reg_ = false; |
135 | meta_ = INTERNET_GAMING_METASERVER; |
136 | port_ = INTERNET_GAMING_PORT; |
137 | @@ -120,32 +122,29 @@ |
138 | |
139 | /// Login to metaserver |
140 | bool InternetGaming::login(const std::string& nick, |
141 | - const std::string& pwd, |
142 | - bool reg, |
143 | + const std::string& authenticator, |
144 | + bool registered, |
145 | const std::string& meta, |
146 | uint32_t port) { |
147 | - assert(state_ == OFFLINE); |
148 | |
149 | - pwd_ = pwd; |
150 | - reg_ = reg; |
151 | + clientname_ = nick; |
152 | + reg_ = registered; |
153 | meta_ = meta; |
154 | port_ = port; |
155 | |
156 | - // If we are not connecting to a registered account, create a random value |
157 | - // to send as password. Used so the metaserver can match our IPv4 and IPv6 connections. |
158 | - // See internet_gaming_protocol.h for more information |
159 | - if (!reg_) { |
160 | - // Admittedly this is a pretty stupid generator. But it should be fine for us |
161 | - static const std::string random_chars = "0123456789ABCDEF"; |
162 | - pwd_ = ""; |
163 | - std::random_device rd; |
164 | - std::mt19937 gen(rd()); |
165 | - std::uniform_int_distribution<> dist(0, random_chars.length() - 1); |
166 | - while (pwd_.length() < 8) { |
167 | - pwd_.push_back(random_chars[dist(gen)]); |
168 | - } |
169 | + if (registered) { |
170 | + authenticator_ = authenticator; |
171 | + } else { |
172 | + authenticator_ = crypto::sha1(nick + authenticator); |
173 | } |
174 | |
175 | + assert(!authenticator_.empty()); |
176 | + |
177 | + return do_login(); |
178 | +} |
179 | + |
180 | +bool InternetGaming::do_login(bool should_relogin) { |
181 | + |
182 | initialize_connection(); |
183 | |
184 | // If we are here, a connection was established and we can send our login package through the |
185 | @@ -154,10 +153,10 @@ |
186 | SendPacket s; |
187 | s.string(IGPCMD_LOGIN); |
188 | s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION)); |
189 | - s.string(nick); |
190 | + s.string(clientname_); |
191 | s.string(build_id()); |
192 | s.string(bool2str(reg_)); |
193 | - s.string(pwd_); |
194 | + s.string(authenticator_); |
195 | net->send(s); |
196 | |
197 | // Now let's see, whether the metaserver is answering |
198 | @@ -169,9 +168,11 @@ |
199 | // paperwork, so we put our feet up and just return. ;) |
200 | if (state_ != CONNECTING) { |
201 | if (state_ == LOBBY) { |
202 | - format_and_add_chat( |
203 | - "", "", true, _("For hosting a game, please take a look at the notes at:")); |
204 | - format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming"); |
205 | + if (!should_relogin) { |
206 | + format_and_add_chat( |
207 | + "", "", true, _("For hosting a game, please take a look at the notes at:")); |
208 | + format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming"); |
209 | + } |
210 | |
211 | // Try to establish a second connection to tell the metaserver about our IPv4 address |
212 | create_second_connection(); |
213 | @@ -191,42 +192,11 @@ |
214 | throw wexception("InternetGaming::relogin: This only makes sense if there was an error."); |
215 | } |
216 | |
217 | - initialize_connection(); |
218 | - |
219 | - // If we are here, a connection was established and we can send our login package through the |
220 | - // socket. |
221 | - log("InternetGaming: Sending relogin request.\n"); |
222 | - SendPacket s; |
223 | - s.string(IGPCMD_RELOGIN); |
224 | - s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION)); |
225 | - s.string(clientname_); |
226 | - s.string(build_id()); |
227 | - s.string(bool2str(reg_)); |
228 | - s.string(pwd_); |
229 | - net->send(s); |
230 | - |
231 | - // Now let's see, whether the metaserver is answering |
232 | - uint32_t const secs = time(nullptr); |
233 | - state_ = CONNECTING; |
234 | - while (INTERNET_GAMING_TIMEOUT > time(nullptr) - secs) { |
235 | - handle_metaserver_communication(); |
236 | - // Check if we are a step further... if yes handle_packet has taken care about all the |
237 | - // paperwork, so we put our feet up and just return. ;) |
238 | - if (state_ != CONNECTING) { |
239 | - if (state_ == LOBBY) { |
240 | - break; |
241 | - } else if (error()) |
242 | - return false; |
243 | - } |
244 | - } |
245 | - |
246 | - if (INTERNET_GAMING_TIMEOUT <= time(nullptr) - secs) { |
247 | - log("InternetGaming: No answer from metaserver!\n"); |
248 | + if (!do_login(true)) { |
249 | return false; |
250 | } |
251 | |
252 | - create_second_connection(); |
253 | - |
254 | + state_ = LOBBY; |
255 | // Client is reconnected, so let's try resend the timeouted command. |
256 | if (waitcmd_ == IGPCMD_GAME_CONNECT) |
257 | join_game(gamename_); |
258 | @@ -238,6 +208,9 @@ |
259 | set_game_playing(); |
260 | } |
261 | |
262 | + log("InternetGaming: Reconnected to metaserver\n"); |
263 | + format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!")); |
264 | + |
265 | return true; |
266 | } |
267 | |
268 | @@ -385,7 +358,7 @@ |
269 | s.string(IGPCMD_TELL_IP); |
270 | s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION)); |
271 | s.string(clientname_); |
272 | - s.string(pwd_); |
273 | + s.string(authenticator_); |
274 | tmpNet->send(s); |
275 | |
276 | // Close the connection |
277 | @@ -416,19 +389,24 @@ |
278 | if (state_ == CONNECTING) { |
279 | if (cmd == IGPCMD_LOGIN) { |
280 | // Clients request to login was granted |
281 | - clientname_ = packet.string(); |
282 | + const std::string assigned_name = packet.string(); |
283 | + if (clientname_ != assigned_name) { |
284 | + format_and_add_chat("", "", true, |
285 | + (boost::format( |
286 | + _("You have been logged in as '%s' since your requested name is already in use or reserved.")) |
287 | + % assigned_name).str()); |
288 | + } |
289 | + clientname_ = assigned_name; |
290 | clientrights_ = packet.string(); |
291 | + if (clientrights_ == INTERNET_CLIENT_UNREGISTERED) { |
292 | + // Might happen that we log in with less rights than we wanted to. |
293 | + // Happens when we are already logged in with another client. |
294 | + reg_ = false; |
295 | + } |
296 | state_ = LOBBY; |
297 | log("InternetGaming: Client %s logged in.\n", clientname_.c_str()); |
298 | return; |
299 | |
300 | - } else if (cmd == IGPCMD_RELOGIN) { |
301 | - // Clients request to relogin was granted |
302 | - state_ = LOBBY; |
303 | - log("InternetGaming: Client %s relogged in.\n", clientname_.c_str()); |
304 | - format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!")); |
305 | - return; |
306 | - |
307 | } else if (cmd == IGPCMD_ERROR) { |
308 | std::string errortype = packet.string(); |
309 | if (errortype != "LOGIN" && errortype != "RELOGIN") { |
310 | @@ -451,9 +429,8 @@ |
311 | cmd.c_str()); |
312 | } |
313 | } |
314 | - |
315 | try { |
316 | - if (cmd == IGPCMD_LOGIN || cmd == IGPCMD_RELOGIN) { |
317 | + if (cmd == IGPCMD_LOGIN) { |
318 | // Login specific commands but not in CONNECTING state... |
319 | log("InternetGaming: Received %s cmd although client is not in CONNECTING state.\n", |
320 | cmd.c_str()); |
321 | @@ -766,16 +743,28 @@ |
322 | |
323 | /// ChatProvider: sends a message via the metaserver. |
324 | void InternetGaming::send(const std::string& msg) { |
325 | + // TODO(Notabilis): Messages can get lost when we are temporarily disconnected from the metaserver, |
326 | + // even when we reconnect again. "Answered" messages like IGPCMD_GAME_CONNECT are resent but chat |
327 | + // messages are not. Resend them after some time when we did not receive the matching IGPCMD_CHAT |
328 | + // command from the server? For global/public messages we could wait for the returned IGPCMD_CHAT |
329 | + // from the metaserver, similar to other commands. What about private messages? Maybe modify the |
330 | + // metaserver to send them back, too? |
331 | if (!logged_in()) { |
332 | format_and_add_chat( |
333 | "", "", true, _("Message could not be sent: You are not connected to the metaserver!")); |
334 | return; |
335 | } |
336 | |
337 | + std::string trimmed = boost::algorithm::trim_copy(msg); |
338 | + if (trimmed.empty()) { |
339 | + // Message is empty or only space characters. We don't want it either way |
340 | + return; |
341 | + } |
342 | + |
343 | SendPacket s; |
344 | s.string(IGPCMD_CHAT); |
345 | |
346 | - if (msg.size() && *msg.begin() == '@') { |
347 | + if (*msg.begin() == '@') { |
348 | // Format a personal message |
349 | std::string::size_type const space = msg.find(' '); |
350 | if (space >= msg.size() - 1) { |
351 | @@ -784,12 +773,19 @@ |
352 | _("Message could not be sent: Was this supposed to be a private message?")); |
353 | return; |
354 | } |
355 | - s.string(msg.substr(space + 1)); // message |
356 | + trimmed = boost::algorithm::trim_copy(msg.substr(space + 1)); |
357 | + if (trimmed.empty()) { |
358 | + format_and_add_chat( |
359 | + "", "", true, |
360 | + _("Message could not be sent: Was this supposed to be a private message?")); |
361 | + return; |
362 | + } |
363 | + s.string(trimmed); // message |
364 | s.string(msg.substr(1, space - 1)); // recipient |
365 | |
366 | format_and_add_chat(clientname_, msg.substr(1, space - 1), false, msg.substr(space + 1)); |
367 | |
368 | - } else if (clientrights_ == INTERNET_CLIENT_SUPERUSER && msg.size() && *msg.begin() == '/') { |
369 | + } else if (clientrights_ == INTERNET_CLIENT_SUPERUSER && *msg.begin() == '/') { |
370 | // This is either a /me command, a super user command, or well... just a chat message |
371 | // beginning |
372 | // with a "/" - let's see... |
373 | @@ -798,15 +794,16 @@ |
374 | std::string cmd, arg; |
375 | std::string temp = msg.substr(1); // cut off '/' |
376 | std::string::size_type const space = temp.find(' '); |
377 | - if (space > temp.size()) |
378 | + if (space > temp.size()) { |
379 | // no argument |
380 | goto normal; |
381 | + } |
382 | |
383 | // get the cmd and the arg |
384 | cmd = temp.substr(0, space); |
385 | - arg = temp.substr(space + 1); |
386 | + arg = boost::algorithm::trim_copy(temp.substr(space + 1)); |
387 | |
388 | - if (cmd == "motd") { |
389 | + if (!arg.empty() && cmd == "motd") { |
390 | SendPacket m; |
391 | m.string(IGPCMD_MOTD); |
392 | // Check whether motd is attached or should be loaded from a file |
393 | @@ -827,19 +824,19 @@ |
394 | } |
395 | // send the request to change the motd |
396 | m.string(arg); |
397 | - net->send(s); |
398 | + net->send(m); |
399 | return; |
400 | - } else if (cmd == "announcement") { |
401 | + } else if (!arg.empty() && cmd == "announcement") { |
402 | // send the request to change the motd |
403 | SendPacket m; |
404 | m.string(IGPCMD_ANNOUNCEMENT); |
405 | m.string(arg); |
406 | - net->send(s); |
407 | + net->send(m); |
408 | return; |
409 | - } else |
410 | + } else { |
411 | // let everything else pass |
412 | goto normal; |
413 | - |
414 | + } |
415 | } else { |
416 | normal: |
417 | s.string(msg); |
418 | |
419 | === modified file 'src/network/internet_gaming.h' |
420 | --- src/network/internet_gaming.h 2017-07-01 08:22:54 +0000 |
421 | +++ src/network/internet_gaming.h 2017-11-26 10:34:25 +0000 |
422 | @@ -51,15 +51,28 @@ |
423 | * The InternetGaming struct. |
424 | */ |
425 | struct InternetGaming : public ChatProvider { |
426 | + |
427 | /// The only instance of InternetGaming -> the constructor is private by purpose! |
428 | static InternetGaming& ref(); |
429 | |
430 | void reset(); |
431 | |
432 | // Login and logout |
433 | + |
434 | void initialize_connection(); |
435 | + |
436 | + /** |
437 | + * Try to login on the metaserver. |
438 | + * @param nick The preferred username. Another username might be chosen by the metaserver if |
439 | + * the requested one is already in use. |
440 | + * @param authenticator If \c registered is \c true, this is the password. Otherwise, it is some unique |
441 | + * id the server can use to identify the user. |
442 | + * @param metaserver The hostname of the metaserver. |
443 | + * @param port The port number of the metaserver. |
444 | + * @return Whether the login was successful. |
445 | + */ |
446 | bool login(const std::string& nick, |
447 | - const std::string& pwd, |
448 | + const std::string& authenticator, |
449 | bool registered, |
450 | const std::string& metaserver, |
451 | uint32_t port); |
452 | @@ -180,6 +193,13 @@ |
453 | bool system, |
454 | const std::string& msg); |
455 | |
456 | + /** |
457 | + * Does the real work of the login. |
458 | + * \param relogin Whether this is a relogin. Only difference is that |
459 | + * on first login a greeting is shown. |
460 | + */ |
461 | + bool do_login(bool relogin = false); |
462 | + |
463 | /// The connection to the metaserver |
464 | std::unique_ptr<NetClient> net; |
465 | |
466 | @@ -187,8 +207,9 @@ |
467 | enum { OFFLINE, CONNECTING, LOBBY, IN_GAME, COMMUNICATION_ERROR } state_; |
468 | |
469 | /// data saved for possible relogin |
470 | - std::string pwd_; |
471 | + std::string authenticator_; |
472 | bool reg_; |
473 | + |
474 | std::string meta_; |
475 | uint16_t port_; |
476 | |
477 | |
478 | === modified file 'src/network/internet_gaming_protocol.h' |
479 | --- src/network/internet_gaming_protocol.h 2017-07-01 08:22:54 +0000 |
480 | +++ src/network/internet_gaming_protocol.h 2017-11-26 10:34:25 +0000 |
481 | @@ -28,12 +28,16 @@ |
482 | |
483 | /** |
484 | * The current version of the in-game network protocol. Client and metaserver |
485 | - * protocol versions must match. |
486 | + * protocol versions must match. The metaserver supports the protocol version of latest stable build |
487 | + * and the newest version in trunk. |
488 | * Used Versions: |
489 | - * 0: Build 19 and before |
490 | + * 0: Build 19 and before [stable, supported] |
491 | * 1: Between build 19 and build 20 - IPv6 support added |
492 | + * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after crashes. |
493 | + * When logging twice with a registered account, the second connection |
494 | + * gets a free username assigned. Dropping RELOGIN command. [supported] |
495 | */ |
496 | -#define INTERNET_GAMING_PROTOCOL_VERSION 1 |
497 | +#define INTERNET_GAMING_PROTOCOL_VERSION 2 |
498 | |
499 | /** |
500 | * The default timeout time after which the client tries to resend a package or even finally closes |
501 | @@ -91,35 +95,43 @@ |
502 | * Every packet starts with a single-byte command code. |
503 | * |
504 | * \note ALL PAYLOADS SHALL BE STRINGS - this is for easier handling and debugging of the |
505 | - * communication |
506 | - * between metaserver and client. If an unsigned or signed value has to be sent, convert it |
507 | - * with |
508 | - * boost::lexical_cast<std::string>. Boolean values should be sent in form of "true" or |
509 | - * "false". |
510 | + * communication between metaserver and client. If an unsigned or signed value has |
511 | + * to be sent, convert it with boost::lexical_cast<std::string>. Boolean values should |
512 | + be sent in form of "true" or "false". |
513 | */ |
514 | |
515 | /** |
516 | - * The nonce: |
517 | - * |
518 | - * The nonce is used on the metaserver to link multiple connections by the same client. This |
519 | - * normally |
520 | - * happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This |
521 | - * way, |
522 | - * the metaserver knows that the clients supports both versions and can show games / offer his game |
523 | + * The UUID: |
524 | + * |
525 | + * The UUID is a semi-permanent ID stored in the configuration file of Widelands. |
526 | + * It has to be stored in the file since it should survive crashes of the game or computer. |
527 | + * If the game is not started for 24 hours, a new one is created to increase privacy. |
528 | + * Basically it allows the metaserver to identify the user even when multiple users try to join with |
529 | + * the same username. Note that the sent UUID differs from the stored one: The sent UUID is |
530 | + * hash(username | stored-id). |
531 | + * In the case of registered players, the password can be used instead of a UUID. The username |
532 | + * alone can not be used for this, especially not for unregistered users: The metaserver can not |
533 | + * differentiate between a second connection by the user and an initial login of another user |
534 | + * (with the same name). |
535 | + * |
536 | + * Use-cases of the UUID: |
537 | + * |
538 | + * 1) Linking connections with IPv4 and IPv6 |
539 | + * The UUID is used on the metaserver to link multiple connections by the same client. This |
540 | + * normally happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This |
541 | + * way, the metaserver knows that the client supports both versions and can show games / offer its game |
542 | * of/for clients with both protocol versions. |
543 | * |
544 | - * When a network client connects to the metaserver with (RE)LOGIN he also sends a nonce. |
545 | - * When "another" netclient connects to the metaserver and sends TELL_IP containing the same nonce, |
546 | + * When a network client connects to the metaserver with (RE)LOGIN it also sends the UUID. |
547 | + * When "another" netclient connects to the metaserver and sends TELL_IP containing the same UUID, |
548 | * it is considered the same game client connecting with another IP. This way, two connections by |
549 | - * IPv4 and |
550 | - * IPv6 can be matched so the server learns both addresses of the client. |
551 | + * IPv4 and IPv6 can be matched so the server learns both addresses of the client. |
552 | * |
553 | - * In the case of registered players, the password can be used instead of a random nonce. The |
554 | - * username alone |
555 | - * can not be used for this, especially not for unregistered users: The metaserver can not |
556 | - * differentiate |
557 | - * between a second connection by the user and an initial login of another user (with the same |
558 | - * name). |
559 | + * 2) Reconnect after crash / network problems. |
560 | + * When Widelands breaks the connection without logging out, the server still assumes that the old |
561 | + * connection is active. So when the player reconnects, another name is chosen. Sending the UUID allows |
562 | + * to reclaim the old name, since the server recognizes that there isn't a second player trying to use |
563 | + * the same name. |
564 | */ |
565 | |
566 | /** |
567 | @@ -132,15 +144,12 @@ |
568 | * closing the connection. The receiver of this command should just close the connection. |
569 | * |
570 | * \note that either party is allowed to close the connection without sending a \ref |
571 | - * IGPCMD_DISCONNECT |
572 | - * command first (in any case, this can happen when the program crashes or network connection |
573 | - * is lost). |
574 | + * IGPCMD_DISCONNECT command first (in any case, this can happen when the program crashes |
575 | + * or network connection is lost). |
576 | * |
577 | * \note If you want to change the payload of this command, change it only by appending new items. |
578 | - * The |
579 | - * reason is that this is the only command that can be sent by the metaserver even when the |
580 | - * protocol |
581 | - * versions differ. |
582 | + * The reason is that this is the only command that can be sent by the metaserver even when the |
583 | + * protocol versions differ. |
584 | * |
585 | */ |
586 | static const std::string IGPCMD_DISCONNECT = "DISCONNECT"; |
587 | @@ -153,62 +162,28 @@ |
588 | * \li string: protocol version |
589 | * \li string: client name |
590 | * \li string: build_id of the client |
591 | - * \li string: whether the client wants to login in to a registered account ("true" or "false" as |
592 | - * string) |
593 | + * \li string: whether the client wants to login in to a registered account |
594 | + * ("true" or "false" as string) |
595 | * \li string: for registered accounts: password in clear text |
596 | - * for unregistered users a random nonce to recognized the matching IPv4 and IPv6 |
597 | - * connections. |
598 | - * for an explanation of the nonce, see above. |
599 | + * for unregistered users the UUID to recognize the matching IPv4 and IPv6 |
600 | + * connections or to reclaim the username after a unintended disconnect. |
601 | + * For an explanation of the UUID, see above. |
602 | * |
603 | * If the metaserver accepts, it replies with a LOGIN command with the following payload: |
604 | * \li string: client name (might be different to the previously chosen one, if the client did |
605 | * NOT login to a registered account and either the chosen is registered or already |
606 | - * used.) |
607 | + * used.) |
608 | * \li string: clients rights (see client rights section above) |
609 | * |
610 | * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will again try to login |
611 | * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not |
612 | * answer" |
613 | * |
614 | - * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_REJECTED |
615 | + * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_ERROR |
616 | */ |
617 | static const std::string IGPCMD_LOGIN = "LOGIN"; |
618 | |
619 | /** |
620 | - * Reinitiate a connection. |
621 | - * |
622 | - * Basically the difference to \ref IGPCMD_LOGIN is, that the metaserver allows the user to go on at |
623 | - * the |
624 | - * place it was before - so if the user was in a running game and only lost connection to the |
625 | - * metaserver, |
626 | - * but not to the game itself, statistics for that game can still be sent and saved. |
627 | - * To ensure everything is fine, the login information will still be checked + if a client with the |
628 | - * name |
629 | - * is still listed as online, the metaserver will ping the client and if the client does not answer |
630 | - * intime |
631 | - * it will be replaced by the user requesting the relogin |
632 | - * |
633 | - * sent by the client, with the following payload: |
634 | - * \li string: protocol version |
635 | - * \li string: client name - the one the metaserver replied at the first login |
636 | - * \li string: build_id of the client |
637 | - * \li string: whether the client wants to login in to a registered account ("false", "true") |
638 | - * \li string: for registered accounts: password in clear text |
639 | - * for unregistered users a random nonce to recognized the matching IPv4 and IPv6 |
640 | - * connections. |
641 | - * for an explanation of the nonce, see above. |
642 | - * |
643 | - * If the metaserver accepts, it replies with a RELOGIN command without any payload. |
644 | - * |
645 | - * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will try to relogin |
646 | - * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not |
647 | - * answer" |
648 | - * |
649 | - * For the case, that the metaserver does not accept the login, it sends a \ref IGPCMD_ERROR "LOGIN" |
650 | - */ |
651 | -static const std::string IGPCMD_RELOGIN = "RELOGIN"; |
652 | - |
653 | -/** |
654 | * Tells the metaserver about a secondary IP address. |
655 | * |
656 | * Assuming the client already has a connection over IPv6 and tries to establish a secondary |
657 | @@ -220,8 +195,8 @@ |
658 | * \li string: protocol version |
659 | * \li string: client name - the one the metaserver replied at the first login |
660 | * \li string: for registered accounts: password in clear text |
661 | - * for unregistered users the random nonce used on login |
662 | - * for an explanation of the nonce, see above. |
663 | + * for unregistered users the UUID used on login |
664 | + * for an explanation of the UUID, see above. |
665 | */ |
666 | static const std::string IGPCMD_TELL_IP = "TELL_IP"; |
667 | |
668 | @@ -291,15 +266,13 @@ |
669 | * The metaserver will echo the message if the client is allowed to send chat messages. |
670 | * |
671 | * The metaserver either broadcasts a chat message to all clients or sends it to the pm recipient |
672 | - * with the |
673 | - * following payload: |
674 | + * with the following payload: |
675 | * \li string: sender (may be empty if it is a system message) |
676 | * \li string: the message |
677 | * \li string: type ("public", "private", "system") |
678 | * |
679 | - * \note system messages are the motd (Sent by the metaserver to the client, after login (but not |
680 | - * relogin) |
681 | - * and after the motd got changed) and announcements by superusers. |
682 | + * \note system messages are the motd (Sent by the metaserver to the client, after login |
683 | + * (but not relogin) and after the motd got changed) and announcements by superusers. |
684 | */ |
685 | static const std::string IGPCMD_CHAT = "CHAT"; |
686 | |
687 | |
688 | === modified file 'src/random/random.cc' |
689 | --- src/random/random.cc 2017-01-25 18:55:59 +0000 |
690 | +++ src/random/random.cc 2017-11-26 10:34:25 +0000 |
691 | @@ -20,6 +20,11 @@ |
692 | #include "random/random.h" |
693 | |
694 | #include <cstdio> |
695 | +#include <random> |
696 | + |
697 | +#include <boost/uuid/uuid.hpp> |
698 | +#include <boost/uuid/uuid_generators.hpp> |
699 | +#include <boost/uuid/uuid_io.hpp> |
700 | |
701 | #include "base/wexception.h" |
702 | #include "io/streamread.h" |
703 | @@ -98,3 +103,8 @@ |
704 | sw.unsigned_32(state0); |
705 | sw.unsigned_32(state1); |
706 | } |
707 | + |
708 | +std::string generate_random_uuid() { |
709 | + static boost::uuids::random_generator gen = boost::uuids::random_generator(); |
710 | + return boost::uuids::to_string(gen()); |
711 | +} |
712 | |
713 | === modified file 'src/random/random.h' |
714 | --- src/random/random.h 2017-01-25 18:55:59 +0000 |
715 | +++ src/random/random.h 2017-11-26 10:34:25 +0000 |
716 | @@ -21,6 +21,7 @@ |
717 | #define WL_RANDOM_RANDOM_H |
718 | |
719 | #include <stdint.h> |
720 | +#include <string> |
721 | |
722 | extern const uint32_t rng_sbox[256]; |
723 | |
724 | @@ -44,4 +45,9 @@ |
725 | |
726 | #define SIMPLE_RAND(x) (((x) >> 8) ^ rng_sbox[(x)&0xff]) |
727 | |
728 | +/// Generates a random UUID, looking like "550e8400-e29b-11d4-a716-446655440000". |
729 | +/// This does not use logic_rand(), but instead a thread local random number |
730 | +/// generator, so do not use in logic code - it will desync. |
731 | +std::string generate_random_uuid(); |
732 | + |
733 | #endif // end of include guard: WL_RANDOM_RANDOM_H |
734 | |
735 | === modified file 'src/ui_fsmenu/CMakeLists.txt' |
736 | --- src/ui_fsmenu/CMakeLists.txt 2017-11-05 20:09:44 +0000 |
737 | +++ src/ui_fsmenu/CMakeLists.txt 2017-11-26 10:34:25 +0000 |
738 | @@ -79,6 +79,7 @@ |
739 | map_io_map_loader |
740 | network |
741 | profile |
742 | + random |
743 | scripting_coroutine |
744 | scripting_lua_interface |
745 | scripting_lua_table |
746 | |
747 | === modified file 'src/ui_fsmenu/internet_lobby.cc' |
748 | --- src/ui_fsmenu/internet_lobby.cc 2017-06-20 19:55:32 +0000 |
749 | +++ src/ui_fsmenu/internet_lobby.cc 2017-11-26 10:34:25 +0000 |
750 | @@ -27,10 +27,12 @@ |
751 | #include "base/macros.h" |
752 | #include "graphic/graphic.h" |
753 | #include "graphic/text_constants.h" |
754 | +#include "network/crypto.h" |
755 | #include "network/gameclient.h" |
756 | #include "network/gamehost.h" |
757 | #include "network/internet_gaming.h" |
758 | #include "profile/profile.h" |
759 | +#include "random/random.h" |
760 | #include "ui_basic/messagebox.h" |
761 | |
762 | FullscreenMenuInternetLobby::FullscreenMenuInternetLobby(char const* const nick, |
763 | @@ -192,8 +194,9 @@ |
764 | Section& s = g_options.pull_section("global"); |
765 | const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str()); |
766 | uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT); |
767 | - |
768 | - InternetGaming::ref().login(nickname_, password_, is_registered_, metaserver, port); |
769 | + std::string auth = is_registered_ ? password_ : s.get_string("uuid"); |
770 | + assert(!auth.empty()); |
771 | + InternetGaming::ref().login(nickname_, auth, is_registered_, metaserver, port); |
772 | } |
773 | |
774 | /// fills the server list |
775 | |
776 | === modified file 'src/ui_fsmenu/multiplayer.cc' |
777 | --- src/ui_fsmenu/multiplayer.cc 2017-02-25 13:27:40 +0000 |
778 | +++ src/ui_fsmenu/multiplayer.cc 2017-11-26 10:34:25 +0000 |
779 | @@ -21,8 +21,10 @@ |
780 | |
781 | #include "base/i18n.h" |
782 | #include "graphic/text_constants.h" |
783 | +#include "network/crypto.h" |
784 | #include "network/internet_gaming.h" |
785 | #include "profile/profile.h" |
786 | +#include "random/random.h" |
787 | #include "ui_basic/messagebox.h" |
788 | #include "wui/login_box.h" |
789 | |
790 | @@ -122,7 +124,9 @@ |
791 | // Try to connect to the metaserver |
792 | const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str()); |
793 | uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT); |
794 | - InternetGaming::ref().login(nickname_, password_, register_, meta, port); |
795 | + std::string auth = register_ ? password_ : s.get_string("uuid"); |
796 | + assert(!auth.empty()); |
797 | + InternetGaming::ref().login(nickname_, auth, register_, meta, port); |
798 | |
799 | // Check whether metaserver send some data |
800 | if (InternetGaming::ref().logged_in()) |
801 | |
802 | === modified file 'src/wlapplication.cc' |
803 | --- src/wlapplication.cc 2017-11-24 09:19:52 +0000 |
804 | +++ src/wlapplication.cc 2017-11-26 10:34:25 +0000 |
805 | @@ -75,6 +75,7 @@ |
806 | #include "network/gamehost.h" |
807 | #include "network/internet_gaming.h" |
808 | #include "profile/profile.h" |
809 | +#include "random/random.h" |
810 | #include "sound/sound_handler.h" |
811 | #include "ui_basic/messagebox.h" |
812 | #include "ui_basic/progresswindow.h" |
813 | @@ -743,6 +744,7 @@ |
814 | s.get_bool("write_syncstreams"); |
815 | s.get_bool("sound_at_message"); |
816 | s.get_bool("transparent_chat"); |
817 | + s.get_string("uuid"); |
818 | s.get_string("registered"); |
819 | s.get_string("nickname"); |
820 | s.get_string("password"); |
821 | @@ -755,6 +757,24 @@ |
822 | s.get_natural("metaserverport"); |
823 | // KLUDGE! |
824 | |
825 | + long int last_start = s.get_int("last_start", 0); |
826 | + if (last_start + 12 * 60 * 60 < time(nullptr)) { |
827 | + // First start of the game or not started for 12 hours. Create a (new) UUID. |
828 | + // For the use of the UUID, see network/internet_gaming_protocol.h |
829 | + s.set_string("uuid", generate_random_uuid()); |
830 | + } |
831 | + s.set_int("last_start", time(nullptr)); |
832 | + |
833 | + // Save configuration now. Otherwise, the UUID is not saved |
834 | + // when the game crashes, loosing part of its advantage |
835 | + try { |
836 | + g_options.write("config", false); |
837 | + } catch (const std::exception& e) { |
838 | + log("WARNING: could not save configuration: %s\n", e.what()); |
839 | + } catch (...) { |
840 | + log("WARNING: could not save configuration"); |
841 | + } |
842 | + |
843 | return true; |
844 | } |
845 |
Continuous integration builds have changed state:
Travis build 2704. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 287726199. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ net_uuid- 2519.
Appveyor build 2519. State: success. Details: https:/