Skip to content

Commit ff7ca97

Browse files
sunnamed434claude
andcommitted
feat: contrast NonPublicized vs Publicized in two plugins
Split the example into two RocketMod plugins pinned to the SAME game build (3.26.3.3), differing only in the redist package: - NonPublicized -> RocketModFix.Unturned.Redist.Server (public API only) - Publicized -> ...Server.Publicized + AllowUnsafeBlocks, reads the non-public Provider.isDedicatedUGCInstalled Verified the contrast: the non-public field fails to compile against the plain redist (CS0117). Add a classic .sln (builds on any SDK), build both in CI, and have Dependabot ignore the two redist packages so the pinned versions stay in lockstep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a4760d8 commit ff7ca97

9 files changed

Lines changed: 201 additions & 71 deletions

.github/dependabot.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ updates:
2020
- "dependencies"
2121
- "github-actions"
2222

23-
# NuGet packages referenced by the plugin (RocketMod + redist).
23+
# NuGet packages referenced by the two plugin projects (RocketMod + redist).
2424
- package-ecosystem: "nuget"
25-
directory: "/"
25+
directories:
26+
- "/Publicized"
27+
- "/NonPublicized"
2628
schedule:
2729
interval: "weekly"
2830
day: "monday"
@@ -41,3 +43,8 @@ updates:
4143
- "nuget"
4244
allow:
4345
- dependency-type: "direct"
46+
ignore:
47+
# Both projects are pinned to the SAME game build so the publicized-vs-not
48+
# contrast stays apples-to-apples. Bump these two by hand, together.
49+
- dependency-name: "RocketModFix.Unturned.Redist.Server"
50+
- dependency-name: "RocketModFix.Unturned.Redist.Server.Publicized"

.github/workflows/build.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ jobs:
1212
with:
1313
dotnet-version: '8.0.x'
1414

15-
# Proves the plugin compiles against the .Publicized redist.
15+
# Builds both plugins; proves each compiles against its redist variant.
1616
- name: Build
1717
run: dotnet build -c Release
1818

19-
- name: Upload plugin DLL
19+
- name: Upload plugin DLLs
2020
uses: actions/upload-artifact@v4
2121
with:
2222
name: UnturnedRedistExample
23-
path: bin/Release/net461/UnturnedRedistExample.dll
23+
path: '**/bin/Release/net461/UnturnedRedistExample.*.dll'
2424
if-no-files-found: error
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Rocket.Core.Logging;
2+
using Rocket.Core.Plugins;
3+
using SDG.Unturned;
4+
5+
namespace UnturnedRedistExample.NonPublicized
6+
{
7+
/// <summary>
8+
/// Uses the plain (non-publicized) redist. Only PUBLIC Unturned members are
9+
/// accessible. Compare with <c>../Publicized</c>, which can also read
10+
/// non-public members. No AllowUnsafeBlocks needed here.
11+
/// </summary>
12+
public class NonPublicizedExamplePlugin : RocketPlugin
13+
{
14+
protected override void Load()
15+
{
16+
// Public Unturned API works fine with the plain redist:
17+
Logger.Log(
18+
$"[UnturnedRedistExample.NonPublicized] Loaded. " +
19+
$"Provider.maxPlayers = {Provider.maxPlayers} (public API).");
20+
21+
// The line below would NOT compile here — the plain redist doesn't
22+
// expose non-public members, so the compiler can't see this field at
23+
// all. To read it, reference the .Publicized redist (see ../Publicized):
24+
//
25+
// var ugc = Provider.isDedicatedUGCInstalled;
26+
// // CS0117: 'Provider' does not contain a definition for 'isDedicatedUGCInstalled'
27+
}
28+
29+
protected override void Unload()
30+
{
31+
Logger.Log("[UnturnedRedistExample.NonPublicized] Unloaded.");
32+
}
33+
}
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net461</TargetFramework>
5+
<!-- No AllowUnsafeBlocks: this plugin only touches public Unturned API. -->
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<!-- RocketMod API. Brings Rocket.Core (RocketPlugin, Logger) + Rocket.API. -->
10+
<PackageReference Include="RocketModFix.Rocket.Unturned" Version="4.23.1" />
11+
12+
<!-- Plain (non-publicized) redist: only public Unturned members are visible.
13+
Pinned to the SAME game build as ../Publicized (see that project). -->
14+
<PackageReference Include="RocketModFix.Unturned.Redist.Server" Version="3.26.3.3" />
15+
16+
<!-- UnityEngine assemblies (Unturned types derive from MonoBehaviour). -->
17+
<PackageReference Include="RocketModFix.UnityEngine.Redist" Version="2022.3.*" />
18+
19+
<!-- Lets `net461` build on Linux/macOS CI without a Windows targeting pack. -->
20+
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Rocket.Core.Logging;
2+
using Rocket.Core.Plugins;
3+
using SDG.Unturned;
4+
5+
namespace UnturnedRedistExample.Publicized
6+
{
7+
/// <summary>
8+
/// Uses the <c>.Publicized</c> redist, so it can read members that are
9+
/// non-public in the stock game. Compare with <c>../NonPublicized</c>, which
10+
/// references the plain redist and can only touch public API.
11+
/// </summary>
12+
public class PublicizedExamplePlugin : RocketPlugin
13+
{
14+
protected override void Load()
15+
{
16+
// isDedicatedUGCInstalled is NON-public in stock Unturned. This line
17+
// compiles only because of the .Publicized redist, and runs only
18+
// because the .csproj sets AllowUnsafeBlocks.
19+
var ugcInstalled = Provider.isDedicatedUGCInstalled;
20+
21+
Logger.Log(
22+
$"[UnturnedRedistExample.Publicized] Loaded. Read NON-public " +
23+
$"Provider.isDedicatedUGCInstalled = {ugcInstalled} via the .Publicized redist.");
24+
}
25+
26+
protected override void Unload()
27+
{
28+
Logger.Log("[UnturnedRedistExample.Publicized] Unloaded.");
29+
}
30+
}
31+
}

UnturnedRedistExample.csproj renamed to Publicized/UnturnedRedistExample.Publicized.csproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22

33
<PropertyGroup>
44
<TargetFramework>net461</TargetFramework>
5-
<!-- Required by any .Publicized redist: lets Mono skip the runtime access
6-
check on the originally-private members. See README. -->
5+
<!-- Required by the .Publicized redist: lets Mono skip the runtime access
6+
check on the originally-private members. -->
77
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
88
</PropertyGroup>
99

1010
<ItemGroup>
1111
<!-- RocketMod API. Brings Rocket.Core (RocketPlugin, Logger) + Rocket.API. -->
1212
<PackageReference Include="RocketModFix.Rocket.Unturned" Version="4.23.1" />
1313

14-
<!-- Unturned's managed assemblies, publicized so the plugin can read
15-
non-public members. Floats to the latest stable build (the redist
16-
auto-updates each game patch); pin a version if you want reproducibility. -->
17-
<PackageReference Include="RocketModFix.Unturned.Redist.Server.Publicized" Version="3.*" />
14+
<!-- Publicized: Unturned's assemblies with non-public members rewritten to
15+
public, so this plugin can read them. Pinned to the SAME game build as
16+
../NonPublicized so the ONLY difference between the two projects is
17+
publicization (bump both together). -->
18+
<PackageReference Include="RocketModFix.Unturned.Redist.Server.Publicized" Version="3.26.3.3" />
1819

19-
<!-- UnityEngine assemblies. Needed because Unturned types (e.g. Provider)
20-
derive from UnityEngine.MonoBehaviour. -->
20+
<!-- UnityEngine assemblies (Unturned types derive from MonoBehaviour). -->
2121
<PackageReference Include="RocketModFix.UnityEngine.Redist" Version="2022.3.*" />
2222

2323
<!-- Lets `net461` build on Linux/macOS CI without a Windows targeting pack. -->

README.md

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
# RocketModFix.Unturned.Redist.Example
22

3-
A minimal [RocketMod](https://github.com/RocketModFix/RocketModFix) plugin showing how to consume the RocketModFix NuGet packages in an Unturned server plugin — in particular the [`RocketModFix.Unturned.Redist`](https://github.com/RocketModFix/RocketModFix.Unturned.Redist) **`.Publicized`** package, which lets a plugin read members that are non-public in the stock game.
3+
Two minimal [RocketMod](https://github.com/RocketModFix/RocketModFix) plugins that show how to consume the RocketModFix NuGet packages in an Unturned server plugin — and, side by side, exactly what the [`RocketModFix.Unturned.Redist`](https://github.com/RocketModFix/RocketModFix.Unturned.Redist) **`.Publicized`** package changes.
44

5-
> This is an example **consumer** plugin. Its own namespace is `UnturnedRedistExample` — i.e. what *you'd* write in your own plugin — it just **references** the RocketModFix packages.
5+
> These are example **consumer** plugins — their namespaces (`UnturnedRedistExample.*`) are what *you'd* write in your own plugin; they just **reference** the RocketModFix packages.
66
7-
## What it demonstrates
7+
## The two plugins
88

9-
[`UnturnedRedistExamplePlugin.cs`](UnturnedRedistExamplePlugin.cs) is one class. On load it reads `SDG.Unturned.Provider.isDedicatedUGCInstalled` — a field that is **non-public** in the stock `Assembly-CSharp.dll` — and logs it:
9+
| Project | Redist package referenced | `AllowUnsafeBlocks` | Reads non-public members? |
10+
| --- | --- | --- | --- |
11+
| [`NonPublicized/`](NonPublicized) | `RocketModFix.Unturned.Redist.Server` | no | ❌ public API only |
12+
| [`Publicized/`](Publicized) | `RocketModFix.Unturned.Redist.Server.Publicized` | **yes** ||
13+
14+
Both reference the **same game build** (`3.26.3.3`) and are otherwise identical RocketMod plugins. The redist package is the only meaningful difference — that's the whole lesson.
15+
16+
### `NonPublicized` — public API only
17+
18+
Reads a **public** member and logs it. The non-public member is shown commented out, because it does **not** compile against the plain redist:
1019

1120
```csharp
12-
protected override void Load()
13-
{
14-
// non-public in stock Unturned; reachable only via the .Publicized redist
15-
var ugcInstalled = Provider.isDedicatedUGCInstalled;
16-
Logger.Log($"[UnturnedRedistExample] Loaded. ... = {ugcInstalled} ...");
17-
}
21+
Logger.Log($"... Provider.maxPlayers = {Provider.maxPlayers} (public API).");
22+
23+
// var ugc = Provider.isDedicatedUGCInstalled;
24+
// // CS0117: 'Provider' does not contain a definition for 'isDedicatedUGCInstalled'
1825
```
1926

20-
That single line is the whole point:
27+
The plain redist doesn't expose non-public members, so the compiler can't see the field at all.
28+
29+
### `Publicized` — non-public members too
30+
31+
Reads `SDG.Unturned.Provider.isDedicatedUGCInstalled`**non-public** in the stock `Assembly-CSharp.dll`:
32+
33+
```csharp
34+
var ugcInstalled = Provider.isDedicatedUGCInstalled; // compiles via the .Publicized package
35+
Logger.Log($"... isDedicatedUGCInstalled = {ugcInstalled} ...");
36+
```
2137

22-
- It **compiles** only because the project references `RocketModFix.Unturned.Redist.Server.Publicized` (the publicizer rewrites that field to public).
23-
- It **runs** only because the `.csproj` sets `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>`, which lets Unturned's Mono runtime skip the access check.
38+
This compiles only because the `.Publicized` package rewrites that member to public, and runs only because the `.csproj` sets `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` (which lets Unturned's Mono runtime skip the access check).
2439

25-
## Packages used
40+
## Packages used (both plugins)
2641

2742
| Package | Why |
2843
| --- | --- |
29-
| [`RocketModFix.Rocket.Unturned`](https://www.nuget.org/packages/RocketModFix.Rocket.Unturned) | The RocketMod API — provides the `RocketPlugin` base class and `Logger`. |
30-
| [`RocketModFix.Unturned.Redist.Server.Publicized`](https://www.nuget.org/packages/RocketModFix.Unturned.Redist.Server.Publicized) | Unturned's server assemblies, **publicized** so the plugin can touch non-public members. Use [`…Server`](https://www.nuget.org/packages/RocketModFix.Unturned.Redist.Server) (without `.Publicized`) if you don't need that. |
44+
| [`RocketModFix.Rocket.Unturned`](https://www.nuget.org/packages/RocketModFix.Rocket.Unturned) | RocketMod API — the `RocketPlugin` base class and `Logger`. |
45+
| [`RocketModFix.Unturned.Redist.Server`](https://www.nuget.org/packages/RocketModFix.Unturned.Redist.Server) / [`.Publicized`](https://www.nuget.org/packages/RocketModFix.Unturned.Redist.Server.Publicized) | Unturned's server assemblies. `.Publicized` additionally exposes non-public members. |
3146
| [`RocketModFix.UnityEngine.Redist`](https://www.nuget.org/packages/RocketModFix.UnityEngine.Redist) | UnityEngine assemblies — required because Unturned types derive from `UnityEngine.MonoBehaviour`. |
3247

3348
See the [redist README](https://github.com/RocketModFix/RocketModFix.Unturned.Redist#using-a-publicized-package) for more on `.Publicized` and `AllowUnsafeBlocks`.
@@ -38,19 +53,20 @@ See the [redist README](https://github.com/RocketModFix/RocketModFix.Unturned.Re
3853
dotnet build -c Release
3954
```
4055

41-
The plugin DLL lands in `bin/Release/net461/UnturnedRedistExample.dll`. CI builds it on every push — see [`.github/workflows/build.yaml`](.github/workflows/build.yaml).
56+
Builds 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`](.github/workflows/build.yaml).
4257

4358
## Verify it loads on a server
4459

4560
1. Install [RocketModFix](https://github.com/RocketModFix/RocketModFix#installation) on an Unturned dedicated server (copy the `Rocket.Unturned` folder into `Modules/`) and start it once so it generates `Servers/<ServerID>/Rocket/`.
46-
2. Copy the built `UnturnedRedistExample.dll` into `Servers/<ServerID>/Rocket/Plugins/` (the DLL goes directly in `Plugins/`, not a subfolder).
47-
3. Start the server and watch the console for:
61+
2. Copy either built DLL into `Servers/<ServerID>/Rocket/Plugins/` (DLLs go directly in `Plugins/`, not a subfolder).
62+
3. Start the server and watch the console:
4863

4964
```
50-
[UnturnedRedistExample] Loaded. Read non-public Provider.isDedicatedUGCInstalled = ... via the .Publicized redist.
65+
[UnturnedRedistExample.NonPublicized] Loaded. Provider.maxPlayers = 24 (public API).
66+
[UnturnedRedistExample.Publicized] Loaded. Read NON-public Provider.isDedicatedUGCInstalled = False via the .Publicized redist.
5167
```
5268

53-
That line confirms two things at once: the plugin **loaded**, and the **publicized** member was read successfully **at runtime** (so `AllowUnsafeBlocks` did its job). If publicization were broken you'd instead get a `FieldAccessException` / "is inaccessible" error.
69+
The `Publicized` line is the key one: it proves the non-public member was read **at runtime** (so `AllowUnsafeBlocks` did its job). If publicization were broken you'd get a `FieldAccessException` ("is inaccessible") instead.
5470

5571
## License
5672

UnturnedRedistExample.sln

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Publicized", "Publicized", "{13690DB7-DDCB-4696-BED3-7D678EA970EF}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnturnedRedistExample.Publicized", "Publicized\UnturnedRedistExample.Publicized.csproj", "{896C0490-A78C-4A21-BFC1-CD7339AFC231}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NonPublicized", "NonPublicized", "{B69B8454-F946-7ECE-B261-90047B2E26B8}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnturnedRedistExample.NonPublicized", "NonPublicized\UnturnedRedistExample.NonPublicized.csproj", "{13E9644A-9A16-4900-AE18-31DF62DD8EE2}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Debug|x64 = Debug|x64
18+
Debug|x86 = Debug|x86
19+
Release|Any CPU = Release|Any CPU
20+
Release|x64 = Release|x64
21+
Release|x86 = Release|x86
22+
EndGlobalSection
23+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
24+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|x64.ActiveCfg = Debug|Any CPU
27+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|x64.Build.0 = Debug|Any CPU
28+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|x86.ActiveCfg = Debug|Any CPU
29+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Debug|x86.Build.0 = Debug|Any CPU
30+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|Any CPU.ActiveCfg = Release|Any CPU
31+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|x64.ActiveCfg = Release|Any CPU
33+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|x64.Build.0 = Release|Any CPU
34+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|x86.ActiveCfg = Release|Any CPU
35+
{896C0490-A78C-4A21-BFC1-CD7339AFC231}.Release|x86.Build.0 = Release|Any CPU
36+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|x64.ActiveCfg = Debug|Any CPU
39+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|x64.Build.0 = Debug|Any CPU
40+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|x86.ActiveCfg = Debug|Any CPU
41+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Debug|x86.Build.0 = Debug|Any CPU
42+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|x64.ActiveCfg = Release|Any CPU
45+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|x64.Build.0 = Release|Any CPU
46+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|x86.ActiveCfg = Release|Any CPU
47+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2}.Release|x86.Build.0 = Release|Any CPU
48+
EndGlobalSection
49+
GlobalSection(SolutionProperties) = preSolution
50+
HideSolutionNode = FALSE
51+
EndGlobalSection
52+
GlobalSection(NestedProjects) = preSolution
53+
{896C0490-A78C-4A21-BFC1-CD7339AFC231} = {13690DB7-DDCB-4696-BED3-7D678EA970EF}
54+
{13E9644A-9A16-4900-AE18-31DF62DD8EE2} = {B69B8454-F946-7ECE-B261-90047B2E26B8}
55+
EndGlobalSection
56+
EndGlobal

UnturnedRedistExamplePlugin.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)