CS2OpenDev.Protos 0.9.6

This package has a SemVer 2.0.0 package version: 0.9.6+cs.build.25218825.
dotnet add package CS2OpenDev.Protos --version 0.9.6
                    
NuGet\Install-Package CS2OpenDev.Protos -Version 0.9.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CS2OpenDev.Protos" Version="0.9.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CS2OpenDev.Protos" Version="0.9.6" />
                    
Directory.Packages.props
<PackageReference Include="CS2OpenDev.Protos" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CS2OpenDev.Protos --version 0.9.6
                    
#r "nuget: CS2OpenDev.Protos, 0.9.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CS2OpenDev.Protos@0.9.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CS2OpenDev.Protos&version=0.9.6
                    
Install as a Cake Addin
#tool nuget:?package=CS2OpenDev.Protos&version=0.9.6
                    
Install as a Cake Tool

CS2OpenDev.Protos

Generated C# protobuf message types for Counter-Strike 2's demo and engine wire protocol.

The descriptors are recovered per-build from the shipped CS2 game binaries by CS2OpenDev-SchemaTracker, not scraped from a third-party mirror, so every release names the exact Steam build it came from.

using CS2OpenSchema.Protos;

var packet = CDemoPacket.Parser.ParseFrom(payload);
var ev     = CMsgSource1LegacyGameEvent.Parser.ParseFrom(userMessage);

What's in it

The demo/engine wire path: demo packets, net messages, entity updates, user messages, usercmds, game events, temp entities. 18 .proto files, ~240,000 lines of generated C#. Namespace CS2OpenSchema.Protos, target net8.0, Google.Protobuf as the only dependency, MIT licence.

Proto short names and .proto filenames are preserved exactly as Valve ships them; only the C# namespace is added. Tools that match on message short names or descriptor filenames keep working, including SchemaTracker's own network_messages.json and demo_messages.json, which join wire-IDs to message types by short name.

Where to get it

NuGet.org, published from the release workflow via trusted publishing. Every version is also mirrored to this org's GitHub Packages feed (https://nuget.pkg.github.com/CS2OpenDev/index.json, which requires an authenticated token even for public packages), and the .nupkg and .snupkg are attached to the GitHub release. Each release's notes name the feeds that version actually reached, so this is checkable rather than something you have to take on trust.

Which CS2 build?

Every assembly carries it, readable without unpacking anything:

typeof(CDemoPacket).Assembly
    .GetCustomAttributes<AssemblyMetadataAttribute>()
    .First(a => a.Key == "CS2BuildId").Value;     // "24537688"

CS2BuildId, CS2Platform and CS2SchemaTrackerCommit are stamped as AssemblyMetadata, and the package version carries +cs.build.{id} as SemVer 2 build metadata. The metadata attributes are the reliable channel: build metadata is not part of a NuGet package's identity, never appears in the .nupkg filename, and feeds are inconsistent about preserving it.

The same facts are in protos/PROVENANCE.json in the repository, alongside the .proto sources.

The curated subset

The full 40-file descriptor set does not compile as one assembly. That is a property of Valve's descriptors, not a packaging choice on our part. Two independent symbol collisions exist:

enums_clientserver.proto:528:3: "k_EMsgGCSystemMessage" is already defined in file
    "base_gcmessages.proto".
    Note that enum values use C++ scoping rules, meaning that enum values are
    siblings of their type, not children of it.
Symbol Defined in And in
k_EMsgGCSystemMessage base_gcmessages.proto:6 (= 4001) enums_clientserver.proto:528 (= 2213)
CMsgProtoBufHeader steammessages.proto:13 steammessages_base.proto:76

proto2 enum values are siblings of their type, so they must be globally unique, and none of these files declares a package, so "global" means the whole assembly.

So the package ships a collision-free, import-closed subset: the transitive import closure over the demo/engine roots. It is verified by compiling from a directory containing only those files, because protoc resolves imports against anything on its include path and a subset that appears to work inside the full directory may not actually be closed.

<details> <summary>The 18 files</summary>

clientmessages          cs_gameevents           cs_usercmd
cstrike15_gcmessages    cstrike15_usermessages  demo
engine_gcmessages       gameevents              gcsdk_gcmessages
netmessages             network_connection      networkbasetypes
source2_steam_stats     steammessages           te
usercmd                 usermessages            valveextensions

Four of these are Steam GC / matchmaking families reachable only through cstrike15_usermessages.protocstrike15_gcmessages.proto. They are not on the demo wire path and are present only to satisfy that import. Pruning cstrike15_gcmessages to the 17-type closure its 6 referenced types actually need would drop steammessages, engine_gcmessages and gcsdk_gcmessages entirely (about 32% of the generated C#) without moving a descriptor byte on anything a demo parser touches. That prune is an open upstream request; it belongs in the tool that materialises the descriptors, not here, so that it is re-derived on every CS2 build rather than hand-maintained and silently reverted by the next refresh.

</details>

Broader coverage will not be one bigger package. The Steam and GC families sit in the collision domains above, so covering them means additional packages split along those domains — never one assembly.

The change of source, measured

These descriptors used to come from Valve's published GameTracking-CS2 tree and are now recovered from the shipped binaries. That is the kind of change a compile cannot check for you: the compiler proves that names resolve, and a field whose number, declared type or label differs still resolves, still compiles, and then misparses in silence.

Cs2DemoKit / DemoViewer.NET diffed the two sources field-by-field before trusting the swap, and reported the result to us (#4). It is their measurement, not ours. 2,753 fields are present in both sources, and zero of them differ in field number, declared type or label. Separately, 47 fields dropped and 50 added; the drops are GC / close-caption cruft and descriptor.proto internals, none of which their parser reads.

The two figures are complementary bookkeeping. "Present in both" is keyed on name, so a rename lands as one drop plus one add rather than as a difference; the counts are where renames and genuine additions and removals show up. The first number is the load-bearing one because of the asymmetry: a field that disappeared out from under you is a compile error, a field you never knew about is inert, and a field that quietly changed number or type is neither. That last case is the one that was measured, and it is empty.

One scope caveat: the comparison covered the 13 .proto files that project compiles, not all 18 shipped here. Our closure adds clientmessages, valveextensions and the three GC-chain files (steammessages, engine_gcmessages, gcsdk_gcmessages), and those five were not part of it. Their full parser suite and analysis suite stayed green across the swap, and their accuracy suite ran 5/5 with no demo regressing against its recorded baseline.

It measures that one transition, once. The descriptors are re-derived on every CS2 build, so each build is still its own diff — which is why the .proto sources are committed here rather than fetched at build time.

Google.Protobuf floor policy

The package exposes generated IMessage types on its public surface, so the Google.Protobuf version is an ABI commitment rather than an implementation detail: every consumer is bound to a compatible major, and raising the floor is a breaking change for anyone pinned below it.

The floor is therefore deliberately conservative and moves rarely. It is Google.Protobuf >= 3.27.0; any 3.x at or above the floor works, and NuGet resolves the highest compatible version in your graph. The floor is raised only for a security fix or a generated-code requirement we cannot work around — never for convenience, and never to track latest. A floor bump ships as a minor version bump of this package, called out in the release notes. A Google.Protobuf major bump (4.x) would ship as a new major of this package.

Grpc.Tools is build-only. It supplies protoc and the MSBuild integration, is marked PrivateAssets="all", and never appears in a consumer's dependency graph. It tracks latest.

Versioning

Patch versions ride the protobuf clock; the major stays in step with CS2OpenDev.Sdk.

The schema dump and the protobuf descriptors move separately: most CS2 patches change the schema without touching a single .proto. If this package rode the SDK's version stream, every schema regen would push a new version, a diff review and a CI run for a byte-identical assembly. Its version.json matches only protos/, src/CS2OpenDev.Protos/ and the normalisation script, so the patch version moves when, and only when, the descriptors do.

The major is the exception, and it is deliberate. It tracks CS2OpenDev.Sdk so the three packages that ship from this repo read as one product; three independent majors on a feed invites pairing CS2OpenDev.Protos 1.x with CS2OpenDev.Sdk 1.x, which was never a real correspondence. The cost is that a major here does not by itself mean the descriptors broke. It also moves when the SDK's schema projection breaks and this package follows to stay in step. protos/PROVENANCE.json plus the CS2BuildId assembly attribute are what actually identify the descriptors.

The converse does not hold, and 4.1 is the case that proves it: the descriptors can break on their own. The staged set is a closure derived upstream, so types can leave this package while cs2_schema.json — and therefore the SDK's API — does not move at all. 188 Game-Coordinator types went that way at CS2 24701871 (migration guide). A removal like that takes a major of its own; only additive descriptor changes land in the patch.

Regenerating

git submodule update --init --depth 1 schema-tracker
python3 scripts/normalize-protos.py          # stage protos/ from the submodule
python3 scripts/normalize-protos.py --check  # verify protos/ is current (CI runs this)

The .proto sources are committed rather than fetched at build time, so a change to Valve's wire format shows up as a reviewable diff on the CS2 build that introduced it. The generated C# is not committed; protoc produces it at build time from the committed sources.

Both ship from the same repository and the same feeds as this package: NuGet.org, GitHub Packages, and the GitHub release page.

  • CS2OpenDev.Sdk: strongly-typed schema classes, enums and game-event records. Zero dependencies.
  • CS2OpenDev.Sdk.GameEvents: decodes CMsgSource1LegacyGameEvent into those typed records. Depends on this package.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on CS2OpenDev.Protos:

Package Downloads
CS2DemoKit.Parser

Zero-copy parser for Counter-Strike 2 (.dem) demo files: the frame and net-message stream, 272 typed game events, and stateful entity tracking with typed entity wrappers.

CS2OpenDev.Sdk.GameEvents

Decodes Counter-Strike 2's legacy game-event wire messages (CMsgSource1LegacyGameEvent) into the strongly-typed event records shipped by CS2OpenDev.Sdk. Includes the event-name to factory registry and a per-fire envelope carrying demo transport context.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.6 29 9/14/2026
0.9.2 58 8/18/2026
0.9.0 754 8/18/2026

Protobuf descriptors recovered from CS2 build 25218825 (windows-x86_64), via CS2OpenDev-SchemaTracker 4d4870c71d31699689b95292909dcb8abc9693a8.