Two minimal RocketMod plugins that read the same non-public Unturned field two ways — via reflection (the plain redist) and via the RocketModFix.Unturned.Redist .Publicized package (a plain field access) — so you can see why publicizing is worth it.
These are example consumer plugins — their namespaces (
UnturnedRedistExample.*) are what you'd write in your own plugin; they just reference the RocketModFix packages. (The redist packages aren't RocketMod-specific — this example just happens to use RocketMod; they work in any Unturned project.)
Both reference the same game build (3.26.3.3) and both read SDG.Unturned.Provider.isDedicatedUGCInstalled — a field that is non-public in the stock Assembly-CSharp.dll. The only difference is how:
| Project | Redist package | How it reads the non-public field |
|---|---|---|
Reflection/ |
RocketModFix.Unturned.Redist.Server |
Reflection — string-keyed, not compile-checked, slower |
Publicized/ |
RocketModFix.Unturned.Redist.Server.Publicized (+ AllowUnsafeBlocks) |
Direct field access — compile-checked, fast |
A direct Provider.isDedicatedUGCInstalled doesn't compile against the plain redist — the package simply doesn't expose non-public members:
CS0117: 'Provider' does not contain a definition for 'isDedicatedUGCInstalled'
So without publicizing, your only option is reflection (Reflection/):
FieldInfo field = typeof(Provider).GetField(
"isDedicatedUGCInstalled", BindingFlags.NonPublic | BindingFlags.Static);
bool ugcInstalled = (bool)field.GetValue(null);Downsides:
- Not compile-checked. The member name is a string — a rename or typo compiles fine and throws at runtime (
NullReferenceExceptionon the missingfield), on your live server. - Harder to read. No IntelliSense, casts everywhere, three lines for one read.
- Slower. Every access pays a reflection lookup + boxing; even caching the
FieldInfostays indirect.
The .Publicized package (Publicized/) rewrites that member to public, so the same read is one normal line:
bool ugcInstalled = Provider.isDedicatedUGCInstalled;- Compile-checked — a rename breaks the build, not your server.
- Reads like ordinary code — full IntelliSense, no casts.
- Plain field-access speed.
The one requirement: set <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in the .csproj, which lets Unturned's Mono runtime skip the access check on the originally-private member.
| Package | Why |
|---|---|
RocketModFix.Rocket.Unturned |
RocketMod API — the RocketPlugin base class and Logger. (Or swap in RocketModFix.LDM.Redist for official upstream Rocket — see below.) |
RocketModFix.Unturned.Redist.Server / .Publicized |
Unturned's server assemblies. .Publicized additionally exposes non-public members. |
RocketModFix.UnityEngine.Redist |
UnityEngine assemblies — required because Unturned types derive from UnityEngine.MonoBehaviour. |
See the redist README for more on .Publicized and AllowUnsafeBlocks.
The RocketPlugin / Logger API can come from either of two packages — pick one (don't reference both; they ship the same Rocket.* assemblies):
| Package | What it is |
|---|---|
RocketModFix.Rocket.Unturned |
The RocketModFix fork of Rocket — same API, plus bug fixes and features. Versioned like the fork (e.g. 4.23.1). Used by both example projects above. |
RocketModFix.LDM.Redist |
The official upstream Rocket — SDG's Legally Distinct Missile, redistributed as-is. Zero-dependency (pair it with any Unturned redist). Versioned by Rocket's own version (e.g. 4.9.3.18). |
Both expose the same Rocket.API / Rocket.Core / Rocket.Unturned types, so switching is a one-line change in the .csproj:
<!-- Official upstream Rocket (LDM) instead of the RocketModFix fork: -->
<PackageReference Include="RocketModFix.LDM.Redist" Version="4.9.3.18" />dotnet build -c ReleaseBuilds both projects (via UnturnedRedistExample.sln); the DLLs land in each project's bin/Release/net461/. CI builds both on every push — see .github/workflows/build.yaml.
-
Install RocketModFix on an Unturned dedicated server (copy the
Rocket.Unturnedfolder intoModules/) and start it once so it generatesServers/<ServerID>/Rocket/. -
Copy either built DLL into
Servers/<ServerID>/Rocket/Plugins/(DLLs go directly inPlugins/, not a subfolder). -
Start the server and watch the console — both read the same value, the hard way and the easy way:
[UnturnedRedistExample.Reflection] Loaded. Read non-public isDedicatedUGCInstalled = False via REFLECTION ... [UnturnedRedistExample.Publicized] Loaded. Read NON-public Provider.isDedicatedUGCInstalled = False via the .Publicized redist.
Seeing those lines confirms each plugin loaded and read the non-public member at runtime. (For the publicized one, that also proves AllowUnsafeBlocks did its job — if publicization were broken you'd get a FieldAccessException instead.)
MIT — see LICENSE.