Theme
Plugin mode blacklist
Which play modes a plugin must not run in, and what the framework does about it.
A plugin declares its modes once, from its own source:
text
ShPluginBlacklist(SH_MODE_BLACKLIST_GHOST_WAR |
SH_MODE_BLACKLIST_MERCENARIES);and the framework takes its row out of the F4 root menu, everything under it with it, for as long as the session is in one of those modes. The declaration is the only input: nothing in any ini can widen or narrow it.
A mask names play modes, and play modes are the only conditions. The front end was one of them until 2026-09-13 and is not any more: the reading it rested on (the GameFlow machine's mode-screen object) latches once a mode screen has been opened, so it was right at startup and wrong after a round trip through a campaign - and ShGetGameState puts the main menu and every lobby in one bucket, so there was no second reading to fall back on. Better no condition than one that is right half the time.
Three cases, and the third is the one that matters:
- declared bits blocked in exactly those;
- declared NONE (0) blocked nowhere - declaring "none" is the only way to be unrestricted;
- not declared at all blocked in Ghost War and Mercenaries. Every third party plugin is in this group by construction, and so is any plugin of ours that has not been taught otherwise. Built-in pages are not plugins (their menu owner is "") and are never blocked.
What is in force comes from one place: ShSelectedPlayMode, the mode the game's own manager was handed. When it cannot be read - the manager has not set a mode yet, or the hook is not armed on this build - nothing is in force and nobody is blocked: a menu that vanishes with no reason to give would be worse than one that stays.
What this cannot do: stop a plugin's code. Plugins run their own threads and carry their own MinHook copy, and the framework has no registry that could suspend them. Blocked means hidden and told; a plugin that wants its work to stop has to stop it. The usual shape is a tick thread that returns early plus a callback that tears down what it installed:
text
static void OnBlocked(int allowed, int mode, void *user) {
(void)mode; (void)user;
if (!allowed) StopMyWork(); // drop hooks, park the thread
else StartMyWork();
}
ShPluginOnBlocked(OnBlocked, NULL);The callback arrives on a framework thread (250 ms poll), once per change, and only when the answer actually flips. Keep it short; it is not holding any lock of ours, so calling back into the framework from it is allowed. See docs/plugin-blacklist.md.
| Export | Summary |
|---|---|
ShPluginBlacklist | Declare which conditions this plugin must not run in - a mask of the bits above. |
ShPluginBlockedBy | What is switching this plugin off right now, as the bits above (SH_MODE_BLACKLIST_*), or 0 when nothing is. |
ShPluginAllowed | 1 when this plugin may run now. |
ShPluginBlacklistModes | The conditions in force for this plugin: what it declared, or the default (Ghost War and Mercenaries) when it never declared anything. |
ShPluginOnBlocked | — |
ShBlacklistName | The name of one bit, for a log line or a menu: "Ghost War", "MERCENARIES", "campaign", "Ghost Mode", "Guerrilla"; "" for 0 or for more than one bit (use ShBlockedText for that). |
ShBlockedText | The bits as one line, for a log: "Ghost War+MERCENARIES". |
ShPluginBlacklistCount | The registry, for a settings page: how many plugins were seen, and one entry per index - its folder name, the conditions in force, and the bits blocking it now (0 for none). |
ShPluginBlacklistAt | See ShPluginBlacklistCount. |
ShPluginBlacklistNotice | One translated line naming the plugins the current conditions have switched off: "Off now (Ghost War): firstperson, freecam (+3)". |
ShPluginBlacklist
c
SH_API int ShPluginBlacklist(uint32_t modes);Declare which conditions this plugin must not run in - a mask of the bits above. Call it once, from your own code: the caller's module is what identifies the plugin. 1 when accepted; 0 with ShLastError saying why - SH_ERR_BAD_ARG when the caller is not a plugin, SH_ERR_REGISTRY_FULL when 64 plugins are already registered.
scripthook.h, line 417
ShPluginBlockedBy
c
SH_API int ShPluginBlockedBy(void);What is switching this plugin off right now, as the bits above (SH_MODE_BLACKLIST_*), or 0 when nothing is.
scripthook.h, line 421
ShPluginAllowed
c
SH_API int ShPluginAllowed(void);1 when this plugin may run now. Cheap; safe to call every frame.
scripthook.h, line 424
ShPluginBlacklistModes
c
SH_API uint32_t ShPluginBlacklistModes(void);The conditions in force for this plugin: what it declared, or the default (Ghost War and Mercenaries) when it never declared anything. 0 for a declarer of "none".
scripthook.h, line 429
ShPluginOnBlocked
c
SH_API int ShPluginOnBlocked(ShPluginBlockedFn fn, void *user);No doc comment in the header.
scripthook.h, line 435
ShBlacklistName
c
SH_API const char *ShBlacklistName(uint32_t bit);The name of one bit, for a log line or a menu: "Ghost War", "MERCENARIES", "campaign", "Ghost Mode", "Guerrilla"; "" for 0 or for more than one bit (use ShBlockedText for that). A translation key: pass it through ShLang before showing it.
scripthook.h, line 441
ShBlockedText
c
SH_API int ShBlockedText(uint32_t bits, char *buf, int cap);The bits as one line, for a log: "Ghost War+MERCENARIES". 1 when something was written.
scripthook.h, line 445
ShPluginBlacklistCount
c
SH_API int ShPluginBlacklistCount(void);The registry, for a settings page: how many plugins were seen, and one entry per index - its folder name, the conditions in force, and the bits blocking it now (0 for none).
scripthook.h, line 450
ShPluginBlacklistAt
c
SH_API int ShPluginBlacklistAt(int i, char *name, int cap,
uint32_t *modes, int *blockedBy);Documented together with ShPluginBlacklistCount just above.
scripthook.h, line 451
ShPluginBlacklistNotice
c
SH_API int ShPluginBlacklistNotice(char *buf, int cap);One translated line naming the plugins the current conditions have switched off: "Off now (Ghost War): firstperson, freecam (+3)". Short on purpose - the Plugins page shows it on the hint line it shares with the "needs a restart" note - and empty with a 0 return when there is nothing to say.
scripthook.h, line 459