Skip to content

GUIDE | Modding for LAN Multiplayer

ChthonVII edited this page Jan 11, 2021 · 9 revisions

A lot of mods don't work in LAN multiplayer. There seem to be 3 reasons for this: (1) A lot of mods were written before LAN multiplayer was added to C&C Remastered; (2) Many modders aren't able to perform testing for LAN multiplayer; and (3) the way LAN multiplayer works in C&C Remastered is just confusing as all hell. This is a short guide that tries to demystify some of the confusing things about LAN multiplayer.

How Multiplayer Worked in the Original Games

The original games worked in a peer-to-peer fashion with each peer simulating the game world in parallel.

The pseudo-random number generators were synced up during the initial load sequence, so all "random" events came out the same way on every peer. If two players launched a match and then just sat there and watched the tiberium grow, the same "random" tiberium growth would happen on both PCs and the match would stay in sync without any communication between peers.

When a player entered input that would change the game world, that input would be digested into a message like "Player W wants unit X to commence a move to position Y on Frame Z," encapsulated into an EventClass object, and placed into the multiplayer communications queue. The contents of the queue would be sent to every peer, and then all peers would execute events from the queue on the same frame, thus staying in sync.

A key part of all of this was a global variable named PlayerPtr, which was a pointer to the HouseClass object belonging to the local player, which enabled the game to distinguish "me" and "not me." Checks against PlayerPtr, or calls to functions that perform checks against PlayerPtr, appear in conditionals in thousands of places throughout the code. Once set, PlayerPtr never changed.

So far, so good? Great, because things are about to get confusing.

How LAN Multiplayer Works in C&C Remastered

The GlyphX clients handle the matchmaking lobby in what appears to be a peer-to-peer fashion. When the match is started, one of the GlyphX clients is chosen to act as the server. Surprisingly, the server is not always the same PC as the host in the matchmaking lobby. The selection appears to be random. From here on out I'm going to refer to one "GlyphX server" in contrast to one or more other "GlyphX clients" who aren't the server.

The other GlyphX clients communicate their inputs to the GlyphX server. Unlike in the original games, these inputs aren't "digested," but rather are raw input stuff like "Player2 is holding the [LEFT CTRL] key" or "Player 3 clicked at (x,y)." The GlyphX server makes calls to its DLL to simulate the game world. And the GlyphX server communicates to the GlyphX clients about what they should display.

Two somewhat surprising things jump out of this picture:

First, the GlyphX clients' DLLs play no role whatsoever. They are never called. They don't do anything. To verify this, I gave one PC a modified DLL with deliberate crash bugs. Sure enough, when that PC was picked as the server, it crashed, but when that PC was not picked as the server, the match proceeded just fine.

Second, the DLL on the server is handling everything. How does it do that? In a word, schizophrenia. Looking over DLLInterface.cpp, most functions have a parameter named player_id and the first thing they do is to call Set_Player_Context(player_id). And what does Set_Player_Context() do? It changes PlayerPtr! After some trial and error, and reading Petroglyph's comments in the code (some of which are misleading), it looks like things work as follows: By default, PlayerPtr points to the server's house. Most of the game logic, even things pertaining to other players, happen with PlayerPtr pointed to the server's house. Functions for processing input happen with PlayerPtr pointing to the house of the player who sent the input. In a few places, there's a loop that iterates through pointing to each player in turn, then goes back to pointing at the server's house after the loop.

Lessons Learned

  • You generally can't trust PlayerPtr or functions that depend on PlayerPtr. Unless you're 100% sure that a given block of code will only ever run in the context of an input-processing function, PlayerPtr will probably point to the server's house when you're expecting it to point somewhere else. So you need to figure out how to make your code work without using PlayerPtr or functions that use it.
  • Some functions that depend on PlayerPtr take a HouseClass* as an optional parameter. So you can still use Is_Selected_By_Player() et al. if you've got a pointer to the house you really want.
  • There is simply no way to get a pointer to the local player's house. This is a direct consequence of the fact that the DLL isn't even running on the client PCs. While it's possible to get a pointer to the house for the PC that the DLL is running on, the answer is always going to be "the server's house." (This was a particularly painful lesson for me.)
  • The best substitute for a usable PlayerPtr is usually going to be some unit's house. If you can express what you want to do in terms of who owns something, you can usually get the pointer you need.
    • Btw, in case you were curious about why units have both TechnoClass.House and TechnoClass.Owner(), here's the answer: TechnoClass.House is a pointer to a HouseClass, so it's of the same type as PlayerPtr and can be used as a replacement. TechnoClass.Owner() returns TechnoClass.House->Class->House, which is a HousesType, which is a char (i.e., a 1-byte int) containing a house number.
  • If you want to display something that's only visible for certain players:
    • Call DLL_Draw_Intercept() directly. (needs extern)
    • DLL_Draw_Intercept() needs an ObjectClass object to do its job. Apparently, the GlyphX client cares about what kind of object you give it. So far as I am aware, TechnoClass and its descendants can be used for arbitrary drawing. However, there's no requirement that what you're drawing is related to or positioned near the object you're using. You just need to make sure the object exists.
    • Modify DLL_Draw_Intercept() to set new_object.VisibleFlags appropriately. To construct the flags, left shift 1 by the player number (House->Class->House) of who you want it to be visible for. For more than one player, repeat and OR together. You can also just use House.Get_Allies() if you want something visible for a house and its allies.
    • The GlyphX client will crash if you give it too many shapes to draw. "Too many" is probably on the order of a few hundred extra shapes beyond what the game would normally draw for a large mission.
    • Don't forget to make an alternative code block for the legacy renderer. This is much easier since PlayerPtr always means "the human player" in single-player games, so you can use it in conditionals.
  • If you want to play a sound that's only audible for certain players:
    • Call On_Sound_Effect() directly so you can set the player_ptr parameter correctly. (You'll need to to make a class-free wrapper function so you can extern it.)
    • In the XML for the sound, you need to use a tag that indicates it should be audible for only one player. IsUnitResponseVO works, and I'm guessing IsGUI and IsHUDVO would too.
    • Loop and call On_Sound_Effect() again with a different player_ptr if you want more than one player to hear it.
    • Do an IsHuman check on the house so the player whose PC is the server doesn't end up hearing the sounds for CPU players.
  • You can probably ignore PRNG safety concerns. In the original games, anything "random" that could affect the game world had to use one of the synced PRNG functions or the match would go out of sync. But since only one DLL is running for LAN multiplayer in C&C Remastered, you can probably just do whatever. Wanna call srand() every time Player3, and only Player3, left clicks? Go right ahead; it probably won't break anything.
  • You don't have to use the event queue, but you probably should anyway. In the original games, this was the only way other peers could learn about your input, and acting on input before it came out of the queue would knock the match out of sync. In C&C Remastered, the queue doesn't go anywhere; the same PC puts stuff in and then turns around and takes it right back out. You can bypass the queue without consequence. In fact, Petroglyph did exactly this with building placement because they wanted an immediate return code for whether it succeeded. However, there are a couple good reasons why you should probably use the event queue anyway:
    • All the schizophrenic PlayerPtr swapping is handled for you, so you don't have to worry about it.
    • It keeps the frame delay between input and action consistent with other commands that do go through the queue.

Testing Your Mod for LAN Multiplayer

Making a second Steam account and buying a second copy of the game is quite a bit of hassle and expense just to be able to test your work. So many people don't do it. I avoided it for a long time and asked noddynod443 for a LOT of help. Fortunately, there is a free, relatively easy alternative -- Goldberg Emulator. To test your mod in LAN multiplayer with only one Steam account, do the following on BOTH computers:

  • Install C&C Remastered via Steam normally.
  • Open the installation directory and rename steam_api.dll and steam_api64.dll so you can switch back to them when you're done testing.
  • Copy the Goldberg Emulator versions of steam_api.dll and steam_api64.dll into the installation directory.
  • Optional configuration of Goldberg emulator:
    • If you don't want your name to be "Goldberg," look in C:\Users\<your user directory>\Goldberg SteamEmu Saves\settings\account_name.txt and change the name.
    • If you want access to your savegames, symlink C:\Users\<your user directory>\Goldberg SteamEmu Saves\1213210\remote\ against the corresponding subdirectory in your Steam userdata directory.
    • If you want access to your unlocked audio tracks, change C:\Users\<your user directory>\Goldberg SteamEmu Saves\settings\user_steam_id.txt to your real Steam ID. (C&C Remastered does its registry entries per Steam ID, and it uses some sort of hash to mark audio tracks unlocked, so you can't simply copy the values across.)
  • If this PC has a firewall, make sure inbound traffic on ports 47584-86, TCP and UDP, is allowed (in addition to the ports the game ordinarily needs). (You can change the port in C:\Users\<your user directory>\Goldberg SteamEmu Saves\settings\listen_port.txt. Open the port specified in that file, plus the two above that.)
  • It should now be possible to play a LAN game between both computers. (It's also possible to connect a PC with the original Steam DLLs with a PC with the Goldberg DLLs. However, the Goldberg PC won't be able to resolve the Steam player's name, and so will label them "Unknown Player.")
  • Remember to activate your mod.
  • Do your testing.
  • Swap the DLLs back.