Skip to content

Play mode selection ​

Group playmode · 10 exports · from forks/gamexueren/scripthook.h (framework 1.0-beta7-wildm1, SH_API_VERSION 2)

Which PvP mode this session is in, read from the game's own mode manager.

The mode is the mode object, and the object is read from CreateGameMode - the description it is handed to build the mode from has a pointer into the game's own mode table as its first field, one entry per mode, stable across sessions (Mercenaries was 38DD178 in all three, the campaign 38DC7F0 every time). That call is hooked (scripthook_playmode.c), its identity is checked against the build before the hook is armed, its argument is read off the watcher thread

  • never inside the hook - and an object that is not in the framework's table is logged and left undecided rather than guessed at (ShPlayModeFingerprint hands the number out for exactly that case; that is how Guerrilla was found).

GameModeManager::SetCurrentGameMode is hooked too, and its argument is logged with every mode, but it is not what names the mode: measured, it is 3 for Ghost War and 2 for the three modes that share it, but it is also 0 for the campaign, Narco Road *and* 2 for Fallen Ghosts with the same campaign object. The same mode content can come with either number, so the argument is recorded and the object decides.

It has to be that call, because nothing else in the engine says it. Measured, in the main menu and in the Ghost War lobby: the GameFlow state name, the GameFlow sub object class hashes, the drawn scene set (empty in the front end), the UI state bits, the input context, the entity counts and the archives read are all identical - PvE is four player co-op, so other humans prove nothing, and every mode reads the same 23 archives because the engine indexes all of them at startup. The mode name in memory is already there in the main menu because it is the button's own label. The objects are reflected (32 byte entries of crc32(name), index and function, and that hash is plain CRC-32), but 312 front end methods resolved against a 24000 name dictionary came back with nothing but the scene interface, and the mode manager's own method names match no method table entry at all. The named lifecycle methods were hooked as a last resort: they fire, with a real dispatcher chain in the stack, but with the same signature for all three modes - page navigation, not a mode choice - and no mode id or name appears in their arguments either.

What this costs and what it does not promise:

  • NONE until the game has set a mode, so asking early is safe and says "not yet" rather than guessing. ShPlayModeHookArmed says whether it ever will on this build.
  • The mode is measured, not documented: an object that is not in the framework's table is left NONE with a log line carrying it (and the argument the game used), rather than guessed at.
  • A different build whose layout differs will not arm the hook, and the answer stays NONE.
  • The mode is known from the moment the game sets it, in the lobby and in a match alike; nothing here needs a mouse, a resolution or a UI scale to be right.
ExportSummary
ShSelectedPlayModeThe mode this session is in, one of ShPlayMode.
ShIsGhostWarModeShSelectedPlayMode() == the mode named.
ShIsMercenariesModeSee ShIsGhostWarMode.
ShIsGhostModeSee ShIsGhostWarMode.
ShIsGuerrillaModeSee ShIsGhostWarMode.
ShPlayModeFingerprintThe mode object's identity, as an RVA into the game's own mode table, or 0 while it is not known.
ShPlayModeEvidenceWhat the answer rests on, for a log line or a menu: "GameModeType 2 with mode object 38DD178 (MERCENARIES)", or why there is none yet.
ShPlayModeHookArmed1 when the game's mode manager is being read, so a caller can tell "not yet" from "not on this build" - and say so, rather than treating NONE as "no mode in particular".
ShPlayModeBitThe bit a mode occupies in a blacklist mask (see the plugin blacklist group below), or 0 for a value that is not a mode.
ShPlayModeNameA mode's name, spelled the way the framework logs it elsewhere: "Ghost War", "MERCENARIES", "campaign", "Ghost Mode", "Guerrilla", and "" for a value that is not a mode.

ShSelectedPlayMode ​

c
SH_API int ShSelectedPlayMode(void);

The mode this session is in, one of ShPlayMode.

NONE means not decided yet, which is also the honest answer while the mode object is still being read - the wait is a watcher tick, 200 ms at most - or when the object is one the framework does not know.

scripthook.h, line 289

ShIsGhostWarMode ​

c
SH_API int ShIsGhostWarMode(void);

ShSelectedPlayMode() == the mode named.

scripthook.h, line 292

ShIsMercenariesMode ​

c
SH_API int ShIsMercenariesMode(void);

Documented together with ShIsGhostWarMode just above.

scripthook.h, line 293

ShIsGhostMode ​

c
SH_API int ShIsGhostMode(void);

Documented together with ShIsGhostWarMode just above.

scripthook.h, line 294

ShIsGuerrillaMode ​

c
SH_API int ShIsGuerrillaMode(void);

Documented together with ShIsGhostWarMode just above.

scripthook.h, line 295

ShPlayModeFingerprint ​

c
SH_API int ShPlayModeFingerprint(void);

The mode object's identity, as an RVA into the game's own mode table, or 0 while it is not known. This is the second half of the answer for the modes that share an argument: the table in scripthook_playmode.c maps each one to a mode, and an object that is not in it is logged by the framework - send that line in and it gets a row.

scripthook.h, line 303

ShPlayModeEvidence ​

c
SH_API int ShPlayModeEvidence(char *buf, int len);

What the answer rests on, for a log line or a menu: "GameModeType 2 with mode object 38DD178 (MERCENARIES)", or why there is none yet. 1 when the mode is known.

scripthook.h, line 308

ShPlayModeHookArmed ​

c
SH_API int ShPlayModeHookArmed(void);

1 when the game's mode manager is being read, so a caller can tell "not yet" from "not on this build" - and say so, rather than treating NONE as "no mode in particular".

scripthook.h, line 313

ShPlayModeBit ​

c
SH_API uint32_t ShPlayModeBit(int mode);

The bit a mode occupies in a blacklist mask (see the plugin blacklist group below), or 0 for a value that is not a mode.

scripthook.h, line 317

ShPlayModeName ​

c
SH_API const char *ShPlayModeName(int mode);

A mode's name, spelled the way the framework logs it elsewhere: "Ghost War", "MERCENARIES", "campaign", "Ghost Mode", "Guerrilla", and "" for a value that is not a mode. It is a translation key: pass it through ShLang before showing it.

scripthook.h, line 323

WildM is an independent community project. It is not affiliated with or endorsed by Ubisoft. Ghost Recon Wildlands is a trademark of Ubisoft Entertainment.