Open Brett208 opened 4 years ago
There are indeed some deep problems here.
The comment about a determined adversary in indeed on point. To avoid an arms race, we should specify this is mostly to protect against accidental desync from loaded module mismatch, and perhaps some protection against low level malicious attempts.
An informational cheat would not alter game state, and so would not cause multiplayer sync issues. We could attempt to detect and report modules, though a malicious user could modify that information locally before sending it. As such, there is no completely reliable way to detect such a cheat, and I would suggest that is out of scope.
The main concern here is then active modifications that change game state. These are perhaps less of a problem for intentional use, since their use would cause a desync. There isn't much point in causing this intentionally, since different players would then effectively be seeing different games. It makes no sense to challenge someone to match, and then cheat in effectively a different game that the opponent is not playing.
This leaves unintentional blunders, where different sets of loaded modules cause unintended different game states. This could potentially be handled at multiplayer game startup. When the checksum of data files is done, we could add additional information exchange about loaded modules. As not all modules can affect gameplay, we might want to limit this to those that do. The only way to know though, is if the modules self report. That opens the possibility for misreporting, or outright lying. We could also go with reporting all modules (and versions/checksums), though that may be overly restrictive, as some modules may be more about fixing non-game related bugs, or different versions of network helpers to get games started. It's also subject to tampering by malicious users. And of course, all of this would require new patches and network protocol upgrades that affect game start. This would likely be written into it's own module, which could be enabled or disabled.
Related to unintentional blunders, are unaware players who may not know about the sync issues caused by a cheat attempt. Either way, it could destroy the fun of the game, as a desync can make one opponent look like a goof who doesn't know how to play the game, or cause crashes on some clients.
To combat low level malicious attempts, and accidental blunders, we could potentially do periodic game state checksums and compare them between players. Maybe we could make use of the save game code to capture game state to an in-memory stream and checksum that, though that may be a bit heavy and cause game stuttering or performance issues. We could also checksum smaller parts of memory, perhaps cycling through the different game components, so it's not all checksummed at once.
The checksum approach would be more robust against blunders or malicious modifications. It's a bit error prone to develop though though, since you likely don't want to include pointers in the checksum, since they would likely have different results on different machines, due to differences in memory load addresses or allocation patterns affected by activity before game start. To avoid address dependent problems, you'd need great detail on the internal game structures to know where addresses are stored in all the different data structures used. Either that, or you need access to a format where those have been removed. Saved game files might be such a format. Though even then, it's not clear that different players could produce binary equivalent saved game files. In fact, they wouldn't since at least the local player number would be different. There may be other differences too, perhaps even trivial differences that would cause a checksum mismatch. Another problem with pointers, is that simply skipping over them might miss important desync information. Instead, you might need to somehow determine if the pointed to objects were somehow equivalent, even though they may be at different addresses for different players. For pointers into arrays, you might convert the pointer to an array index. For non-array structures, the problem would be more involved.
Due to the difficulties here, I'm tempted to take the ostrich approach, and bury my head in the sand (not that any ostrich anywhere has ever been observed to have ever actually done this). Pretending this isn't a problem is perhaps a fairly practical approach. If people want to have a fun time, don't mess with game settings when playing multiplayer. Just leave things as stock. If you want to modify things, consider using a separate copy of the game for local play.
As we develop more built in modules, it might be nice to somehow compare and validate modules between Outpost 2 instances within the match.
This can prevent accidental oversights between players who have tinkered with the .ini file from affecting a game. It could also deter actual nefarious behaviour, although I'm certain there would be ways around anything we implemented if someone were determined enough.
Currently, the game's version number can be modified by op2ext. This works if the expectation is only a single module remains in questions. If the becomes many choices, it breaks down quickly.
There could be space for creating an official settings list for multiplayer modules and only setting the version number properly if they are invoked. I'm not a fan because it means the authors of op2ext would have to chose the official list and it wouldn't leave space for creativity in changing the rules in certain matches.
Should we add a property to a module to indicate if it matters for multiplayer? For example, it will never really matter if the GarageSaveLoadBug module is present in multiplayer. Also, the color module shouldn't really affect gameplay if only loaded by some (Although it would be silly to play with different colors). We may also have synchronization issues if the logic is different between different issues of Outpost 2. May be best to just strictly enforce that all modules are the same between each instance of Outpost 2 regardless of affect on gameplay.
I think for now I would be okay continuing to add modules as long as they would not significantly change a multiplayer match. For example, I wouldn't mind pushing the cheat disable function into a BuiltInModule, but this should probably not be done until it can be detected which machine chooses not to load the module.
Maybe someday in the future there could be a radio button list of modules in the multiplayer lobby so players could dynamically select certain game rules. Or certain modules could be toggled by the mission DLL to prevent so much memory hacking in mission dlls. But those are probably better for a future issue.