Reimplement multiplayer command ownership and document network flow - #220
Reimplement multiplayer command ownership and document network flow#220Krarilotus wants to merge 3 commits into
Conversation
|
Added native system-message evidence to the linked network-flow note in 8a9c79d. The player-removal branch calls the identity translator at Crusader 0x490755, then immediately calls removePlayerFromLobby at 0x49075B without reloading ECX: another concrete caller for the receiver-preservation requirement in this implementation. The note also distinguishes host timing/hash resets from timed-command dispatch and records the system type constants against the SDK header. This update changes documentation only; the C++ implementation and its previously reported 6,156 differential comparisons are unchanged. The larger receive routine is analyzed, not claimed newly reimplemented. |
| uint player = 0; | ||
| if (this->currentGameMode == Game::GM_SOLITARY || this->currentGameMode == Game::GM_SKIRMISH_SINGLE_PLAYER) { | ||
| return this->currentPlayerSlotID; | ||
| } | ||
|
|
||
| // Do not return on the first match: the native function lets the last | ||
| // matching slot win, including duplicate or sentinel-valued handles. | ||
| if (this->currentPlayerFullIDArray[1] == playerHandle) | ||
| player = 1; | ||
| if (this->currentPlayerFullIDArray[2] == playerHandle) | ||
| player = 2; | ||
| if (this->currentPlayerFullIDArray[3] == playerHandle) | ||
| player = 3; | ||
| if (this->currentPlayerFullIDArray[4] == playerHandle) | ||
| player = 4; | ||
| if (this->currentPlayerFullIDArray[5] == playerHandle) | ||
| player = 5; | ||
| if (this->currentPlayerFullIDArray[6] == playerHandle) | ||
| player = 6; | ||
| if (this->currentPlayerFullIDArray[7] == playerHandle) | ||
| player = 7; | ||
| if (this->currentPlayerFullIDArray[8] == playerHandle) | ||
| player = 8; | ||
| return player; |
There was a problem hiding this comment.
I checked this myself, since functions of this size can usually be brute forced and are good to learn stuff.
Here is the body I managed to match:
uint player = 0;
if (this->currentGameMode == Game::GM_SOLITARY) {
return this->currentPlayerSlotID;
}
if (this->currentGameMode == Game::GM_SKIRMISH_SINGLE_PLAYER) {
return this->currentPlayerSlotID;
}
for (int i = 1; i < 9; ++i) {
if (this->currentPlayerFullIDArray[i] == playerHandle) {
player = i;
}
}
return player;The loop does actually not contribute to the matching value. Cases that show these strongly repeating patterns with clear "run" values, like increasing ints, are sometimes unrolled loops.
The not matching part was actually the early return. Here the knowledge was in the assembly. Such chained logic tests like in your version usually resolve to two checks after another. In this case however, the first check in the originals assembly was directly followed by the early return body, then comes the second check that actually jmps back to the firsts return body, a sign that these two where different early returns.
I recommend our cheat sheet in the project root. It is not the best read stylewise and I should clean it a bit one day, but it contains a lot of weird patterns we noticed in the compiler behaviour.
TL;DR: Reimplements how the game translates a multiplayer sender into the player who owns a command. Documents the native paths for timed commands, immediate commands and synchronization.
Adds
GameSynchronyState::translateMultiplayerIDsIntoPlayerIDsat Crusader0x47EAF0in readable C++03 using the existing receiver layout. Single-player modes ignore the supplied handle; multiplayer scans slots 1–8 and keeps the last match, including duplicates and sentinels. Slot 0 is excluded. The implementation preserves the ECX receiver used by native callers.Validation: MSVC 2005 SP1
/O2compilation, formatter and diff checks pass. 3,078 native/C++ differential cases per executable (6,156 total) pass with memory bounds, no state writes, relocated receivers, ECX/callee-saved registers and thiscall stack cleanup checked. The checker validates original executable hashes; Extreme's corresponding routine is0x47ECC0and its local-slot offset differs. Reproduction instructions are intools/reimplementation-tests/PLAYER-IDENTITY.md.The compiled function is 137 bytes, the same size as the native routine, but its single-player return block is placed differently. This is not an exact-byte or 100% reccmp claim. A fresh full RelWithDebInfo
OpenSHC.dllbuild passes, with linked reccmp at 85.29%; the soft status entry records that score. A current Windows SDK manifest tool is used locally. No DLL deployment or live multiplayer test was performed. Generated headers, resolver activation and default source selection are unchanged.The linked wiki note records native command timing/ownership, the 24-bit wire timestamp, immediate dispatch outside the timed queue, host/roster transitions and chunked state resynchronization. Those larger routines were analyzed, not newly reimplemented here. The implementation covers the identity translator; the other routines are documented analysis.