Merge lp:~widelands-dev/widelands/bug-better-syncstreams into lp:widelands
- bug-better-syncstreams
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 8981 | ||||||||
Proposed branch: | lp:~widelands-dev/widelands/bug-better-syncstreams | ||||||||
Merge into: | lp:widelands | ||||||||
Diff against target: |
544 lines (+273/-11) 16 files modified
data/tribes/buildings/productionsites/atlanteans/mill/init.lua (+1/-1) src/economy/economy.cc (+2/-1) src/economy/request.cc (+1/-1) src/logic/cmd_queue.cc (+1/-2) src/logic/filesystem_constants.h (+1/-0) src/logic/game.cc (+51/-0) src/logic/game.h (+66/-1) src/logic/map_objects/bob.cc (+1/-0) src/logic/map_objects/map_object.cc (+7/-1) src/logic/map_objects/tribes/battle.cc (+1/-1) src/logic/map_objects/tribes/program_result.h (+1/-0) src/logic/map_objects/tribes/worker.cc (+2/-2) src/network/gameclient.cc (+5/-1) src/network/gamehost.cc (+3/-0) src/wlapplication.cc (+7/-0) utils/syncstream/syncexcerpt-to-text.py (+123/-0) |
||||||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/bug-better-syncstreams | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
GunChleoc | Approve | ||
Review via email: mp+361922@code.launchpad.net |
Commit message
Print more information in syncstreams. Create additional smaller syncstream files containing the last few seconds leading to a desync.
Description of the change
[Ready for review and merge]
Since I am not really able to get useful information out of the existing syncstream files, this branch adds further information to the syncstream describing the type of the syncstream entries.
Additionally, create a smaller syncstream extract file next to the syncstream file which contains only the last few seconds. This should be enough to debug the desync but reduces the size of the to-be-uploaded files.
bunnybot (widelandsofficial) wrote : | # |
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 4411. State: passed. Details: https:/
Appveyor build 4201. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I think this is an excellent idea, so I've done a preliminary code review. Let me know when you are finished for a final review.
Notabilis (notabilis27) wrote : | # |
Thanks for the review. I answered some of your comments below and will push a merge-ready version later on.
GunChleoc (gunchleoc) wrote : | # |
Answered.
Notabilis (notabilis27) wrote : | # |
As far as I am concerned, this branch is ready for review and merge now.
GunChleoc (gunchleoc) wrote : | # |
Code LGTM, not tested
Notabilis (notabilis27) wrote : | # |
I tested(?) it by enforcing a desync (modifying player.cc and using std::rand() as ID for new economies, then changing economy targets ingame). The new *.wse files were created and could be parsed by the python script. No idea what else can be tested.
GunChleoc (gunchleoc) wrote : | # |
I have done some similar testing. I'd like to see a few changes:
1. WLApplication:
2. There are no instructions in the Python script.
I'd like to at least see a usage hint when it's called without parameters.
Notabilis (notabilis27) wrote : | # |
I wrote some documentation, do you think it is understandable and sufficient?
GunChleoc (gunchleoc) wrote : | # |
Looks good to me, let's have it!
@bunnybot merge
Preview Diff
1 | === modified file 'data/tribes/buildings/productionsites/atlanteans/mill/init.lua' |
2 | --- data/tribes/buildings/productionsites/atlanteans/mill/init.lua 2018-09-06 08:21:35 +0000 |
3 | +++ data/tribes/buildings/productionsites/atlanteans/mill/init.lua 2019-02-10 11:27:32 +0000 |
4 | @@ -74,7 +74,7 @@ |
5 | } |
6 | }, |
7 | produce_blackroot_flour = { |
8 | - -- TRANSLATORS: Completed/Skipped/Did not start grinding blackrootbecause ... |
9 | + -- TRANSLATORS: Completed/Skipped/Did not start grinding blackroot because ... |
10 | descname = _"grinding blackroot", |
11 | actions = { |
12 | -- No check whether we need blackroot_flour because blackroots cannot be used for anything else. |
13 | |
14 | === modified file 'src/economy/economy.cc' |
15 | --- src/economy/economy.cc 2019-02-09 08:42:15 +0000 |
16 | +++ src/economy/economy.cc 2019-02-10 11:27:32 +0000 |
17 | @@ -711,6 +711,7 @@ |
18 | // alerts, so add info to the sync stream here. |
19 | { |
20 | ::StreamWrite& ss = game.syncstream(); |
21 | + ss.unsigned_8(SyncEntry::kProcessRequests); |
22 | ss.unsigned_8(req.get_type()); |
23 | ss.unsigned_8(req.get_index()); |
24 | ss.unsigned_32(req.target().serial()); |
25 | @@ -1051,7 +1052,7 @@ |
26 | // to avoid potential future problems caused by the supplies_ changing |
27 | // under us in some way. |
28 | ::StreamWrite& ss = game.syncstream(); |
29 | - ss.unsigned_32(0x02decafa); // appears as facade02 in sync stream |
30 | + ss.unsigned_8(SyncEntry::kHandleActiveSupplies); |
31 | ss.unsigned_32(assignments.size()); |
32 | |
33 | for (const auto& temp_assignment : assignments) { |
34 | |
35 | === modified file 'src/economy/request.cc' |
36 | --- src/economy/request.cc 2018-12-13 07:24:01 +0000 |
37 | +++ src/economy/request.cc 2019-02-10 11:27:32 +0000 |
38 | @@ -380,7 +380,7 @@ |
39 | assert(is_open()); |
40 | |
41 | ::StreamWrite& ss = game.syncstream(); |
42 | - ss.unsigned_32(0x01decafa); // appears as facade01 in sync stream |
43 | + ss.unsigned_8(SyncEntry::kStartTransfer); |
44 | ss.unsigned_32(target().serial()); |
45 | ss.unsigned_32(supp.get_position(game)->serial()); |
46 | |
47 | |
48 | === modified file 'src/logic/cmd_queue.cc' |
49 | --- src/logic/cmd_queue.cc 2018-12-13 07:24:01 +0000 |
50 | +++ src/logic/cmd_queue.cc 2019-02-10 11:27:32 +0000 |
51 | @@ -114,8 +114,7 @@ |
52 | |
53 | if (dynamic_cast<GameLogicCommand*>(&c)) { |
54 | StreamWrite& ss = game_.syncstream(); |
55 | - static uint8_t const tag[] = {0xde, 0xad, 0x00}; |
56 | - ss.data(tag, 3); // provide an easy-to-find pattern as debugging aid |
57 | + ss.unsigned_8(SyncEntry::kRunQueue); |
58 | ss.unsigned_32(c.duetime()); |
59 | ss.unsigned_32(static_cast<uint32_t>(c.id())); |
60 | } |
61 | |
62 | === modified file 'src/logic/filesystem_constants.h' |
63 | --- src/logic/filesystem_constants.h 2018-11-09 08:00:35 +0000 |
64 | +++ src/logic/filesystem_constants.h 2019-02-10 11:27:32 +0000 |
65 | @@ -49,6 +49,7 @@ |
66 | const std::string kReplayDir = "replays"; |
67 | const std::string kReplayExtension = ".wrpl"; |
68 | const std::string kSyncstreamExtension = ".wss"; |
69 | +const std::string kSyncstreamExcerptExtension = ".wse"; |
70 | // The time in seconds for how long old replays/syncstreams should be kept |
71 | // around, in seconds. Right now this is 4 weeks. |
72 | constexpr double kReplayKeepAroundTime = 4 * 7 * 24 * 60 * 60; |
73 | |
74 | === modified file 'src/logic/game.cc' |
75 | --- src/logic/game.cc 2019-02-09 08:42:15 +0000 |
76 | +++ src/logic/game.cc 2019-02-10 11:27:32 +0000 |
77 | @@ -37,6 +37,7 @@ |
78 | #include "base/macros.h" |
79 | #include "base/time_string.h" |
80 | #include "base/warning.h" |
81 | +#include "build_info.h" |
82 | #include "economy/economy.h" |
83 | #include "game_io/game_loader.h" |
84 | #include "game_io/game_preload_packet.h" |
85 | @@ -87,6 +88,8 @@ |
86 | void Game::SyncWrapper::start_dump(const std::string& fname) { |
87 | dumpfname_ = fname + kSyncstreamExtension; |
88 | dump_.reset(g_fs->open_stream_write(dumpfname_)); |
89 | + current_excerpt_id_ = 0; |
90 | + excerpts_buffer_[current_excerpt_id_].clear(); |
91 | } |
92 | |
93 | void Game::SyncWrapper::data(void const* const sync_data, size_t const size) { |
94 | @@ -114,6 +117,8 @@ |
95 | log("Writing to syncstream file %s failed. Stop synctream dump.\n", dumpfname_.c_str()); |
96 | dump_.reset(); |
97 | } |
98 | + assert(current_excerpt_id_ < kExcerptSize); |
99 | + excerpts_buffer_[current_excerpt_id_].append(static_cast<const char*>(sync_data), size); |
100 | } |
101 | |
102 | target_.data(sync_data, size); |
103 | @@ -604,6 +609,51 @@ |
104 | } |
105 | |
106 | /** |
107 | + * Switches to the next part of the syncstream excerpt. |
108 | + */ |
109 | +void Game::report_sync_request() { |
110 | + syncwrapper_.current_excerpt_id_ = (syncwrapper_.current_excerpt_id_ + 1) % SyncWrapper::kExcerptSize; |
111 | + syncwrapper_.excerpts_buffer_[syncwrapper_.current_excerpt_id_].clear(); |
112 | +} |
113 | + |
114 | +/** |
115 | + * Triggers writing of syncstream excerpt and adds the playernumber of the desynced player |
116 | + * to the stream. |
117 | + * Playernumber should be negative when called by network clients |
118 | + */ |
119 | +void Game::report_desync(int32_t playernumber) { |
120 | + if (syncwrapper_.dumpfname_.empty()) { |
121 | + log("Error: A desync occurred but no filename for the syncstream has been set."); |
122 | + return; |
123 | + } |
124 | + // Replace .wss extension of syncstream file with .wse extension for syncstream extract |
125 | + std::string filename = syncwrapper_.dumpfname_; |
126 | + assert(syncwrapper_.dumpfname_.length() > kSyncstreamExtension.length()); |
127 | + filename.replace(filename.length() - kSyncstreamExtension.length(), |
128 | + kSyncstreamExtension.length(), kSyncstreamExcerptExtension); |
129 | + std::unique_ptr<StreamWrite> file(g_fs->open_stream_write(filename)); |
130 | + assert(file != nullptr); |
131 | + // Write revision, branch and build type of this build to the file |
132 | + file->unsigned_32(build_id().length()); |
133 | + file->text(build_id()); |
134 | + file->unsigned_32(build_type().length()); |
135 | + file->text(build_type()); |
136 | + file->signed_32(playernumber); |
137 | + // Write our buffers to the file. Start with the oldest one |
138 | + const size_t i2 = (syncwrapper_.current_excerpt_id_ + 1) % SyncWrapper::kExcerptSize; |
139 | + size_t i = i2; |
140 | + do { |
141 | + file->text(syncwrapper_.excerpts_buffer_[i]); |
142 | + syncwrapper_.excerpts_buffer_[i].clear(); |
143 | + i = (i + 1) % SyncWrapper::kExcerptSize; |
144 | + } while (i != i2); |
145 | + file->unsigned_8(SyncEntry::kDesync); |
146 | + file->signed_32(playernumber); |
147 | + // Restart buffers |
148 | + syncwrapper_.current_excerpt_id_ = 0; |
149 | +} |
150 | + |
151 | +/** |
152 | * Calculate the current synchronization checksum and copy |
153 | * it into the given array, without affecting the subsequent |
154 | * checksumming process. |
155 | @@ -625,6 +675,7 @@ |
156 | */ |
157 | uint32_t Game::logic_rand() { |
158 | uint32_t const result = rng().rand(); |
159 | + syncstream().unsigned_8(SyncEntry::kRandom); |
160 | syncstream().unsigned_32(result); |
161 | return result; |
162 | } |
163 | |
164 | === modified file 'src/logic/game.h' |
165 | --- src/logic/game.h 2018-12-13 07:24:01 +0000 |
166 | +++ src/logic/game.h 2019-02-10 11:27:32 +0000 |
167 | @@ -63,6 +63,55 @@ |
168 | gs_ending |
169 | }; |
170 | |
171 | +// The entry types that are written to the syncstream |
172 | +// The IDs are a number in the higher 4 bits and the length in bytes in the lower 4 bits |
173 | +// Keep this synchronized with utils/syncstream/syncexcerpt-to-text.py |
174 | +enum SyncEntry : uint8_t { |
175 | + // Used in: |
176 | + // game.cc Game::report_desync() |
177 | + // Parameters: |
178 | + // s32 id of desynced user, -1 when written on client |
179 | + kDesync = 0x14, |
180 | + // map_object.cc CmdDestroyMapObject::execute() |
181 | + // u32 object serial |
182 | + kDestroyObject = 0x24, |
183 | + // economy.cc Economy::process_requests() |
184 | + // u8 request type |
185 | + // u8 request index |
186 | + // u32 target serial |
187 | + kProcessRequests = 0x36, |
188 | + // economy.cc Economy::handle_active_supplies() |
189 | + // u32 assignments size |
190 | + kHandleActiveSupplies = 0x44, |
191 | + // request.cc Request::start_transfer() |
192 | + // u32 target serial |
193 | + // u32 source(?) serial |
194 | + kStartTransfer = 0x58, |
195 | + // cmd_queue.cc CmdQueue::run_queue() |
196 | + // u32 duetime |
197 | + // u32 command id |
198 | + kRunQueue = 0x68, |
199 | + // game.h Game::logic_rand_seed() |
200 | + // u32 random seed |
201 | + kRandomSeed = 0x74, |
202 | + // game.cc Game::logic_rand() |
203 | + // u32 random value |
204 | + kRandom = 0x84, |
205 | + // map_object.cc CmdAct::execute() |
206 | + // u32 object serial |
207 | + // u8 object type (see map_object.h MapObjectType) |
208 | + kCmdAct = 0x95, |
209 | + // battle.cc Battle::Battle() |
210 | + // u32 first soldier serial |
211 | + // u32 second soldier serial |
212 | + kBattle = 0xA8, |
213 | + // bob.cc Bob::set_position() |
214 | + // u32 bob serial |
215 | + // s16 position x |
216 | + // s16 position y |
217 | + kBobSetPosition = 0xB8 |
218 | +}; |
219 | + |
220 | class Player; |
221 | class MapLoader; |
222 | class PlayerCommand; |
223 | @@ -187,9 +236,13 @@ |
224 | |
225 | void logic_rand_seed(uint32_t const seed) { |
226 | rng().seed(seed); |
227 | + syncstream().unsigned_8(SyncEntry::kRandomSeed); |
228 | + syncstream().unsigned_32(seed); |
229 | } |
230 | |
231 | StreamWrite& syncstream(); |
232 | + void report_sync_request(); |
233 | + void report_desync(int32_t playernumber); |
234 | Md5Checksum get_sync_hash() const; |
235 | |
236 | void enqueue_command(Command* const); |
237 | @@ -279,7 +332,8 @@ |
238 | target_(target), |
239 | counter_(0), |
240 | next_diskspacecheck_(0), |
241 | - syncstreamsave_(false) { |
242 | + syncstreamsave_(false), |
243 | + current_excerpt_id_(0) { |
244 | } |
245 | |
246 | ~SyncWrapper() override; |
247 | @@ -304,6 +358,17 @@ |
248 | std::unique_ptr<StreamWrite> dump_; |
249 | std::string dumpfname_; |
250 | bool syncstreamsave_; |
251 | + // Use a cyclic buffer for storing parts of the syncstream |
252 | + // Currently used buffer |
253 | + size_t current_excerpt_id_; |
254 | + // (Arbitrary) count of buffers |
255 | + // Syncreports seem to be requested from the network clients every game second so this |
256 | + // buffer should be big enough to store the last 32 seconds of the game actions leading |
257 | + // up to the desync |
258 | + static constexpr size_t kExcerptSize = 32; |
259 | + // Array of byte buffers |
260 | + // std::string is used as a binary buffer here |
261 | + std::string excerpts_buffer_[kExcerptSize]; |
262 | } syncwrapper_; |
263 | |
264 | GameController* ctrl_; |
265 | |
266 | === modified file 'src/logic/map_objects/bob.cc' |
267 | --- src/logic/map_objects/bob.cc 2018-12-13 07:24:01 +0000 |
268 | +++ src/logic/map_objects/bob.cc 2019-02-10 11:27:32 +0000 |
269 | @@ -893,6 +893,7 @@ |
270 | // randomly generated movements. |
271 | if (upcast(Game, game, &egbase)) { |
272 | StreamWrite& ss = game->syncstream(); |
273 | + ss.unsigned_8(SyncEntry::kBobSetPosition); |
274 | ss.unsigned_32(serial()); |
275 | ss.signed_16(coords.x); |
276 | ss.signed_16(coords.y); |
277 | |
278 | === modified file 'src/logic/map_objects/map_object.cc' |
279 | --- src/logic/map_objects/map_object.cc 2018-12-13 07:24:01 +0000 |
280 | +++ src/logic/map_objects/map_object.cc 2019-02-10 11:27:32 +0000 |
281 | @@ -49,6 +49,7 @@ |
282 | } |
283 | |
284 | void CmdDestroyMapObject::execute(Game& game) { |
285 | + game.syncstream().unsigned_8(SyncEntry::kDestroyObject); |
286 | game.syncstream().unsigned_32(obj_serial); |
287 | |
288 | if (MapObject* obj = game.objects().get_object(obj_serial)) |
289 | @@ -94,10 +95,15 @@ |
290 | } |
291 | |
292 | void CmdAct::execute(Game& game) { |
293 | + game.syncstream().unsigned_8(SyncEntry::kCmdAct); |
294 | game.syncstream().unsigned_32(obj_serial); |
295 | |
296 | - if (MapObject* const obj = game.objects().get_object(obj_serial)) |
297 | + if (MapObject* const obj = game.objects().get_object(obj_serial)) { |
298 | + game.syncstream().unsigned_8(static_cast<uint8_t>(obj->descr().type())); |
299 | obj->act(game, arg); |
300 | + } else { |
301 | + game.syncstream().unsigned_8(static_cast<uint8_t>(MapObjectType::MAPOBJECT)); |
302 | + } |
303 | // the object must queue the next CMD_ACT itself if necessary |
304 | } |
305 | |
306 | |
307 | === modified file 'src/logic/map_objects/tribes/battle.cc' |
308 | --- src/logic/map_objects/tribes/battle.cc 2018-12-13 07:24:01 +0000 |
309 | +++ src/logic/map_objects/tribes/battle.cc 2019-02-10 11:27:32 +0000 |
310 | @@ -65,7 +65,7 @@ |
311 | assert(first_soldier->get_owner() != second_soldier->get_owner()); |
312 | { |
313 | StreamWrite& ss = game.syncstream(); |
314 | - ss.unsigned_32(0x00e111ba); // appears as ba111e00 in a hexdump |
315 | + ss.unsigned_8(SyncEntry::kBattle); |
316 | ss.unsigned_32(first_soldier->serial()); |
317 | ss.unsigned_32(second_soldier->serial()); |
318 | } |
319 | |
320 | === modified file 'src/logic/map_objects/tribes/program_result.h' |
321 | --- src/logic/map_objects/tribes/program_result.h 2018-12-13 07:24:01 +0000 |
322 | +++ src/logic/map_objects/tribes/program_result.h 2019-02-10 11:27:32 +0000 |
323 | @@ -21,6 +21,7 @@ |
324 | #define WL_LOGIC_MAP_OBJECTS_TRIBES_PROGRAM_RESULT_H |
325 | |
326 | namespace Widelands { |
327 | +// Don't change this values, they are used as hardcoded array indices |
328 | enum ProgramResult { None = 0, Failed = 1, Completed = 2, Skipped = 3 }; |
329 | enum ProgramResultHandlingMethod { Fail, Complete, Skip, Continue, Repeat }; |
330 | } // namespace Widelands |
331 | |
332 | === modified file 'src/logic/map_objects/tribes/worker.cc' |
333 | --- src/logic/map_objects/tribes/worker.cc 2018-12-13 07:24:01 +0000 |
334 | +++ src/logic/map_objects/tribes/worker.cc 2019-02-10 11:27:32 +0000 |
335 | @@ -2651,7 +2651,7 @@ |
336 | } |
337 | } |
338 | |
339 | -/** Check if militray sites have becom visible by now. |
340 | +/** Check if militray sites have become visible by now. |
341 | * |
342 | * After the pop in prepare_scouts_worklist, |
343 | * the latest entry of scouts_worklist is the next MS to visit (if known) |
344 | @@ -2684,7 +2684,7 @@ |
345 | const Player& player, |
346 | std::vector<ImmovableFound>& found_sites) { |
347 | |
348 | - // If there are many enemy sites, push a random walk request into queue evry third finding. |
349 | + // If there are many enemy sites, push a random walk request into queue every third finding. |
350 | uint32_t haveabreak = 3; |
351 | |
352 | for (const ImmovableFound& vu : found_sites) { |
353 | |
354 | === modified file 'src/network/gameclient.cc' |
355 | --- src/network/gameclient.cc 2018-12-13 07:24:01 +0000 |
356 | +++ src/network/gameclient.cc 2019-02-10 11:27:32 +0000 |
357 | @@ -860,6 +860,7 @@ |
358 | int32_t const time = packet.signed_32(); |
359 | d->time.receive(time); |
360 | d->game->enqueue_command(new CmdNetCheckSync(time, [this] { sync_report_callback(); })); |
361 | + d->game->report_sync_request(); |
362 | break; |
363 | } |
364 | |
365 | @@ -891,8 +892,11 @@ |
366 | case NETCMD_INFO_DESYNC: |
367 | log("[Client] received NETCMD_INFO_DESYNC. Trying to salvage some " |
368 | "information for debugging.\n"); |
369 | - if (d->game) |
370 | + if (d->game) { |
371 | d->game->save_syncstream(true); |
372 | + // We don't know our playernumber, so report as -1 |
373 | + d->game->report_desync(-1); |
374 | + } |
375 | break; |
376 | |
377 | default: |
378 | |
379 | === modified file 'src/network/gamehost.cc' |
380 | --- src/network/gamehost.cc 2019-02-09 09:03:23 +0000 |
381 | +++ src/network/gamehost.cc 2019-02-10 11:27:32 +0000 |
382 | @@ -1875,6 +1875,7 @@ |
383 | } |
384 | |
385 | log("[Host]: Requesting sync reports for time %i\n", d->syncreport_time); |
386 | + d->game->report_sync_request(); |
387 | |
388 | SendPacket packet; |
389 | packet.unsigned_8(NETCMD_SYNCREQUEST); |
390 | @@ -1916,6 +1917,8 @@ |
391 | i, d->syncreport.str().c_str(), client.syncreport.str().c_str()); |
392 | |
393 | d->game->save_syncstream(true); |
394 | + // Create syncstream excerpt and add faulting player number |
395 | + d->game->report_desync(i); |
396 | |
397 | SendPacket packet; |
398 | packet.unsigned_8(NETCMD_INFO_DESYNC); |
399 | |
400 | === modified file 'src/wlapplication.cc' |
401 | --- src/wlapplication.cc 2018-12-13 07:24:01 +0000 |
402 | +++ src/wlapplication.cc 2019-02-10 11:27:32 +0000 |
403 | @@ -1470,6 +1470,13 @@ |
404 | if (is_autogenerated_and_expired(filename)) { |
405 | log("Delete syncstream %s\n", filename.c_str()); |
406 | g_fs->fs_unlink(filename); |
407 | + assert(filename.length() > kSyncstreamExtension.length()); |
408 | + std::string filename_wse = filename.substr(0, filename.length() - kSyncstreamExtension.length()); |
409 | + filename_wse += kSyncstreamExcerptExtension; |
410 | + if (g_fs->file_exists(filename_wse)) { |
411 | + log("Delete syncstream excerpt %s\n", filename_wse.c_str()); |
412 | + g_fs->fs_unlink(filename_wse); |
413 | + } |
414 | } |
415 | } |
416 | |
417 | |
418 | === added file 'utils/syncstream/syncexcerpt-to-text.py' |
419 | --- utils/syncstream/syncexcerpt-to-text.py 1970-01-01 00:00:00 +0000 |
420 | +++ utils/syncstream/syncexcerpt-to-text.py 2019-02-10 11:27:32 +0000 |
421 | @@ -0,0 +1,123 @@ |
422 | +#!/usr/bin/env python3 |
423 | + |
424 | +import struct |
425 | +import sys |
426 | + |
427 | +# WARNING!! |
428 | +# Keep this file in sync with the enum SyncEntry in src/logic/game.h |
429 | + |
430 | +def handle_1(f): |
431 | + print("Desync:") |
432 | + # Read 4 bytes and interprete them as an signed 32 bit integer |
433 | + print(" Desynced player:", struct.unpack('<i', f.read(4))[0]) |
434 | + |
435 | +def handle_2(f): |
436 | + print("DestroyObject:") |
437 | + print(" Serial:", struct.unpack('<I', f.read(4))[0]) |
438 | + |
439 | +def handle_3(f): |
440 | + print("ProcessRequests:") |
441 | + print(" Type:", struct.unpack('<B', f.read(1))[0]) |
442 | + print(" Index:", struct.unpack('<B', f.read(1))[0]) |
443 | + print(" Serial:", struct.unpack('<I', f.read(4))[0]) |
444 | + |
445 | +def handle_4(f): |
446 | + print("HandleActiveSupplies:") |
447 | + print(" Size:", struct.unpack('<I', f.read(4))[0]) |
448 | + |
449 | +def handle_5(f): |
450 | + print("StartTransfer:") |
451 | + print(" Target serial:", struct.unpack('<I', f.read(4))[0]) |
452 | + print(" Source serial:", struct.unpack('<I', f.read(4))[0]) |
453 | + |
454 | +def handle_6(f): |
455 | + print("RunQueue:") |
456 | + print(" Duetime:", struct.unpack('<I', f.read(4))[0]) |
457 | + print(" Command:", struct.unpack('<I', f.read(4))[0]) |
458 | + |
459 | +def handle_7(f): |
460 | + print("RandomSeed:") |
461 | + print(" Seed:", struct.unpack('<I', f.read(4))[0]) |
462 | + |
463 | +def handle_8(f): |
464 | + print("Random:") |
465 | + print(" Number:", struct.unpack('<I', f.read(4))[0]) |
466 | + |
467 | +def handle_9(f): |
468 | + print("CmdAct:") |
469 | + print(" Serial:", struct.unpack('<I', f.read(4))[0]) |
470 | + # See MapObjectType in map_object.h for decoding the type |
471 | + print(" Actor type:", struct.unpack('<B', f.read(1))[0]) |
472 | + |
473 | +def handle_A(f): |
474 | + print("Battle:") |
475 | + print(" Serial first soldier:", struct.unpack('<I', f.read(4))[0]) |
476 | + print(" Serial second soldier:", struct.unpack('<I', f.read(4))[0]) |
477 | + |
478 | +def handle_B(f): |
479 | + print("BobSetPosition:") |
480 | + print(" Serial:", struct.unpack('<I', f.read(4))[0]) |
481 | + print(" Position X:", struct.unpack('<h', f.read(2))[0]) |
482 | + print(" Position y:", struct.unpack('<h', f.read(2))[0]) |
483 | + |
484 | +handlers = { |
485 | + 1: handle_1, |
486 | + 2: handle_2, |
487 | + 3: handle_3, |
488 | + 4: handle_4, |
489 | + 5: handle_5, |
490 | + 6: handle_6, |
491 | + 7: handle_7, |
492 | + 8: handle_8, |
493 | + 9: handle_9, |
494 | + 10: handle_A, |
495 | + 11: handle_B |
496 | + } |
497 | + |
498 | + |
499 | +if len(sys.argv) != 2: |
500 | + print("Usage: %s filename.wse > outfile.txt\n" % sys.argv[0]) |
501 | + |
502 | + print("Parses the hex-data in the given Widelands Syncstream Excerpt") |
503 | + print("into human readable form and echos it to stdout.\n") |
504 | + |
505 | + print("You should do this with both the *.wse files of the host and") |
506 | + print("the desyncing client. Which player desynced is also reported") |
507 | + print("at the beginning of the output file of the host.") |
508 | + print("Afterwards, the resulting files can be compared with the") |
509 | + print("'diff' command. For correctly syncronized games the resulting") |
510 | + print("files should be the same, but most likely there will be some") |
511 | + print("small differences in the files (e.g., commands that are only") |
512 | + print("executed for one of the players).") |
513 | + print("There probably will be a large block of commands present at") |
514 | + print("the beginning of only one of the files, this is normal and") |
515 | + print("can be ignored.\n") |
516 | + |
517 | + print("To find out more about the commands and where in the Widelands") |
518 | + print("code they are used, see the enum SyncEntry in src/logic/game.h") |
519 | + sys.exit(1) |
520 | + |
521 | +with open(sys.argv[1], "rb") as f: |
522 | + print("Created with %s(%s)" |
523 | + % (str(f.read(struct.unpack('<I', f.read(4))[0]).decode()), |
524 | + str(f.read(struct.unpack('<I', f.read(4))[0]).decode())), |
525 | + end='') |
526 | + playernumber = struct.unpack('<i', f.read(4))[0] |
527 | + if playernumber < 0: |
528 | + print(" as client") |
529 | + else: |
530 | + print(" as host, desynced client = %i" % playernumber) |
531 | + byte = f.read(1) |
532 | + while byte: |
533 | + # Parse byte |
534 | + b = ord(byte) |
535 | + cmd = b >> 4 |
536 | + length = b & 0x0F |
537 | + if cmd in handlers: |
538 | + handlers[cmd](f) |
539 | + else: |
540 | + # Unknown command. Skip as many bytes as the entry is long |
541 | + print("Warning: Unknown command code '%s', skipping %i bytes" % (cmd, length)) |
542 | + for i in range(0, length): |
543 | + byte = f.read(1) |
544 | + byte = f.read(1) |
Continuous integration builds have changed state:
Travis build 4409. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 481989790. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ bug_better_ syncstreams- 4200.
Appveyor build 4200. State: success. Details: https:/